[Last update: 6-AUGUST-2024]
Fitmate Size (also called Recommendation package) is a solution for online size recommendation of shoes. It combines recommendation based on a foot scan (widget) with recommendation based on similar shoes (Shoe2Shoe). Fitmate Size also offers an account management solution with past purchases and family members management. Fitmate Size is a web application that can be integrated within an iframe or even in a webview in a native application on an iPhone or Android.
There are two possible ways of integrating Fitmate Size to a retailer's webpage:
The requirements for a RETAILER are:
Fitmate Size is loaded asynchronously and does not block the main web page. When it is successfully loaded, the _open_recpkg_button button becomes visible. After clicking the _open_recpkg_button button the GUI opens which guides the user to the size recommendation result.
In case all relevant data is available (e.g. in another shoe model), no user interaction is needed. The size recommendation is done in the background so the result is automatically displayed in the _display_recpkg_recommendation_div element.
The input parameters should be provided via Meta data. The language of the app is set depending on the retailer website's html "lang" tag or the input "lang" meta tag.
When the RETAILER uses the global shoe database the following parameters need to be provided:
<!-- the stock keeping unit of the shoe model -->
<meta itemprop="sku" content="104074-04">
<!-- the brand of the shoe model (only used for display) -->
<meta itemprop="brand" content="PUMA">
<!-- the name of the shoe model -->
<meta itemprop="shoe_name" content="An amazing model">
<!-- the image of the shoe model -->
<meta itemprop="image" content="https://url-to-image/an_amazing_model.png">
sku
in this case is an internal RETAILER's name of the shoe model. To use that, a mapping of RETAILER's SKU to SafeSize SKU needs to be provided. This is usually a part of the shoe registration process.
If the SKU mapping is not provided, then the sku
parameter needs to be equal to the name in the SafeSize GSDB. In this case, the brand is a mandatory parameter.
When the RETAILER is integrated with SafeSize and uses a dedicated shoe database, then sku
is replaced by shoe_model
<!-- the code of the shoe model -->
<meta itemprop="shoe_model" content="AirZoomStructure21ShieldMA__NIKE">
<!-- the brand of the shoe model -->
<meta itemprop="brand" content="NIKE">
<!-- the name of the shoe model -->
<meta itemprop="shoe_name" content="An amazing model">
<!-- the image of the shoe model -->
<meta itemprop="image" content="https://url-to-image/an_amazing_model.png">
<!-- sizing code of the user -->
<meta itemprop="sc" content="9010346254594"/>
<!-- id of the account, can be uuid or email -->
<meta itemprop="account_id" content="100d84a4-5698-11ec-bf63-0242ac130002">
<meta itemprop="account_id" content="safesize@safesize.com">
<!-- age of the user, options: C | A -->
<meta itemprop="age" content="A">
<!-- gender of the user, options: M | F -->
<meta itemprop="gender" content="M">
<!-- sport category of the shoe, options: BROWN|RUNNING|OUTDOOR|BASKETBALL|... -->
<meta itemprop="sport" content="RUNNING">
<!-- language, options: ISO language code - https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes -->
<meta itemprop="lang" content="en"/>
<!-- retailer's stock of sizes for shoe model, options: list of sizes separated by "|" character -->
<meta itemprop="stock_sizes" content="10|10.5|11.5|12">
<!-- array of the user's current past purchases in an agreed upon format, the array needs to be converted to a string using JSON.stringify for implementation with metatags. This list is used to register the purchases in the list in SafeSize's system -->
<meta itemprop="past_purchases" content='[{"id_order": "513110","date_add": "2021-06-25 02:32:44","id_shop": "1","id_warehouse": "2","sku": "CQ9326-401","subSku": "IDP25787--IDPA199361","ean13": "2011272885224","upc": "194501527323","sizeEUR": "42","itemType": "shoes","brand": "Nike","product_quantity": "1","product_quantity_refunded": "0","product_quantity_return": "0","total_price_tax_excl": "108.250000","total_price_tax_incl": "129.900000"}]'>
New customer (does not have a scan or account)
<!-- use only required configuration -->
Customer with an account (the account can contain multiple family members with or without scan)
The account_id parameter can be uuid or email.
<!-- use required configuration -->
<!-- add account id -->
<meta itemprop="account_id" content="100d84a4-5698-11ec-bf63-0242ac130002">
Customer with a scan
<!-- use required configuration -->
<!--add sizing code of the user -->
<meta itemprop="sc" content="9010346254594"/>
<button id="_open_recpkg_button" style="display: none"></button>
<div id="_display_recpkg_recommendation_div" style="display: none"></div>
This is example code snippet (code for you implementation will be provided separately):
<script>
(function (w, d, s, g, n, a, b, r, t, e) {
t = d.createElement(s); e = d.getElementsByTagName(s)[0];
t.defer = 1; t.id = '_' + n + '_loader'; t.src = g + encodeURIComponent(a) + '.js'; e.parentNode.insertBefore(t, e);
w['_recpkgApi'] = n; n = (w[n] = w[n] || {}); n.cId = a, n.btn = b; n.rec = r;
})(window, document, 'script',
'https://url-to-recpkg-loader/',
'recpkg_api',
'loaderSAFESIZEDEMO-c5e7f0ee-32ea-445a-b15e-6cf92d6cc19f',
'_open_recpkg_button',
'_display_recpkg_recommendation_div');
</script>
_open_recpkg_button and _display_recpkg_recommendation_div can be the same html element.
The RETAILER can decide to control the Fitmate Size results by themselves. Doing this, brings two advantages: styling the Fitmate Size elements as desired and allowing the END USER to add a size to cart directly from the Fitmate Size GUI.
When the Fitmate Size calculates the recommendation it will emit a CustomEvent safesize-recommended-size. The event will contain recommendation data in the detail.recommendation property.
Possible recommendation text(displayString) and status code(recommendationStatusCode) combinations:
The displayString depends on the language of the app and can appear as localized text, but the recommendationStatusCode stays the same.
Example code to handle the event:
document.addEventListener('safesize-recommended-size', function (event){
var data = event.detail.recommendation;
console.log(data.modelCode) // "AirZoomStructure21ShieldMA__NIKE"
console.log(data.size) // "8.5"
console.log(data.sizeSys) // "US"
console.log(data.sizeSysGender) // "M"
console.log(data.displaySize) // "8.5 US"
console.log(data.displayString) // "Recommended size for Jane NoScan: 8.5 US"
console.log(data.recommendationStatusCode) // 101
console.log(data.personName) // "Jane NoScan"
});
If no size is recommended the data.size
and data.sizeSys
will be undefined
.
sizeSysGender is relevant for US sizing system only (M or F).
The RETAILER can now display the recommendation on his page.
When the END USER presses the add to cart button in the Fitmate Size GUI, it will emit a CustomEvent safesize-to-cart. The event will contain the data of the selected size in the detail.recommendation property.
Example code to handle the event:
document.addEventListener('safesize-to-cart', function (event){
var data = event.detail.recommendation;
console.log(data.modelCode) // "AirZoomStructure21ShieldMA__NIKE"
console.log(data.size) // "8.5"
console.log(data.sizeSys) // "US"
console.log(data.sizeSysGender) // "M"
console.log(data.displaySize) // "8.5 US"
console.log(data.displayString) // "Recommended size for Jane NoScan: 8.5 US"
console.log(data.personName) // "Jane NoScan"
});
sizeSysGender is relevant for US sizing system only (M or F). The RETAILER can now add the selected size to the END USER's cart.
The api contains 4 functions:
function: creates iframe and returns the recommended size text in an event
function: creates iframe, opens GUI and returns the recommended size text in an event
function: closes GUI if it is already open
Please also see the previous section Integration of Fitmate Size with Metatags for a more detailed explanation.
inputParameters, this is an object with the following parameters: required
optional
referenceData (optional), this is a whole element, elementID, index, object, function etc., used to know where or how to display and handle the API response on retailer's side. Can be null.
Example calls and input parameters:
var inputParameters1 = {
image: "https://safesizepublic.ucscentral.com/data/pictures/models/3020612-003__UNDER_ARMOUR.001.jpg",
shoe_name: "Curry 6",
sku: "18899",
brand:"Under Armour",
account_id: "100d8d50-5698-11ec-bf63-0242ac130002"
}
var inputParameters2 = {
image: "https://safesizepublic.ucscentral.com/data/pictures/models/130690-301__NIKE.001.jpg",
shoe_name: "Air Jordan 12 Retro",
sku: "17918",
brand:"Nike",
sc: "901284141"
}
var inputParameters3 = {
lang: "en",
image: "https://safesizepublic.ucscentral.com/data/pictures/models/130690-301__NIKE.001.jpg",
shoe_name: "Air Jordan 12 Retro",
sku: "17918",
brand:"Nike",
sc: "901284141",
stock_sizes: "10|10.5|11|11.5"
}
var inputParameters4 = {
lang: "en",
image: "https://safesizepublic.ucscentral.com/data/pictures/models/130690-301__NIKE.001.jpg",
shoe_name: "Air Jordan 12 Retro",
sku: "17918",
brand:"Nike",
account_id: "100d8d50-5698-11ec-bf63-0242ac130002",
past_purchases: [
{
"id_order": "513110",
"date_add": "2021-06-25 02:32:44",
"id_shop": "1",
"id_warehouse": "2",
"sku": "CQ9326-401",
"subSku": "IDP25787--IDPA199361",
"ean13": "2011272885224",
"upc": "194501527323",
"sizeEUR": "42",
"itemType": "shoes",
"brand": "Nike",
"product_quantity": "1",
"product_quantity_refunded": "0",
"product_quantity_return": "0",
"total_price_tax_excl": "108.250000",
"total_price_tax_incl": "129.900000"
}
]
}
var referenceData = element;
recpkg_api.getRecommendation(inputParameters, referenceData);
recpkg_api.openRecommendationGUI(inputParameters, referenceData);
recpkg_api.closeRecommendationGUI();
This is example code snippet (code for you implementation will be shipped separately):
<script>
(function (w, d, s, g, n, a, b, r, t, e) {
t = d.createElement(s); e = d.getElementsByTagName(s)[0];
t.defer = 1; t.id = '_' + n + '_loader'; t.src = g + encodeURIComponent(a) + '.js'; e.parentNode.insertBefore(t, e);
w['_recpkgApi'] = n; n = (w[n] = w[n] || {}); n.cId = a, n.btn = b; n.rec = r;
})(window, document, 'script',
'https://url-to-recpkg-loader/',
'recpkg_api',
'loaderSAFESIZEDEMO-c5e7f0ee-32ea-445a-b15e-6cf92d6cc19f');
</script>
We suggest to load snippet in the head meta tag in a separate .js file. Description of parameters:
The iframe sends back different events, you access the events data using the event.detail property.
safesize-loaded : event is triggered after neccessary scripts are loaded and API functions become available. This is handy when calling functions automatically on page load to make sure that everything is loaded in order to avoid potential errors on slower connections.
Example code to handle the event:
document.addEventListener('safesize-loaded', function (event) {
console.log('EV: safesize-loaded');
});
safesize-closed : event is triggered after widget is closed. can be used to disable scrolling on iframe ancestor elements, etc.
Example code to handle the event:
document.addEventListener('safesize-closed', function (event) {
console.log('EV: safesize-closed');
});
safesize-recommended-size : returns the object, which contains all the data needed to handle the displaying of data on the retailer's side:
Possible recommendation text(displayString) and status code(recommendationStatusCode) combinations:
The displayString depends on the language of the app and can appear as localized text, but the recommendationStatusCode stays the same.
Possible recommendationType values:
Possible status codes for persons:
Possible status codes for shoe models:
Example code to handle the event:
document.addEventListener('safesize-recommended-size', function (event){
console.log('EV: safesize-recommended-size', event.detail);
const {
referenceData,
recommendation,
statusPerson,
statusModel,
guiAvailable
} = event.detail;
console.log(recommendation.modelCode) // "AirZoomStructure21ShieldMA__NIKE"
console.log(recommendation.size) // "8.5"
console.log(recommendation.sizeSys) // "US"
console.log(recommendation.sizeSysGender) // "M"
console.log(recommendation.displaySize) // "8.5 US"
console.log(recommendation.displayString) // "Recommended size for Jane NoScan: 8.5 US"
console.log(recommendation.personName) // "Jane NoScan"
});
Example of the event.detail property for the event:
{
"guiAvailable": true,
"recommendation": {
"modelCode": "18899",
"size": "44",
"displaySize": "44 EU",
"sizeSys": "EU",
"sizeSysGender": "",
"displayString": "Recommended size for Johnny HasScan: 44 EU",
"personName": "Johnny HasScan"
},
"statusPerson": {
"statusCode": 10,
"statusMessage": "OK"
},
"statusModel": {
"statusCode": 10,
"statusMessage": "OK"
},
"referenceData": "some-data"
}
Example of the event.detail property for the event in case shoe is not available for recommendation:
{
"guiAvailable": false,
"statusPerson": {
"statusCode": null,
"statusMessage": ""
},
"statusModel": {
"statusCode": 11,
"statusMessage": "Shoe model does not exist."
},
"referenceData": "some-data"
}
safesize-to-cart : returns the recommendation object, which contains all the data needed to handle the displaying of data on the retailer's side: this includes modelCode(shoe_model or sku), size, sizeSys(sizing system EU, US, etc.), sizeSysGender (gender used for selecting US sizing system), displaySize, name of the customer and the full displayString. Also return the referenceData which was passed by the retailer
Example code to handle the event:
document.addEventListener('safesize-to-cart', function (event) {
console.log('EV: safesize-to-cart', event.detail);
});
Example of the event.detail property for the event:
{
"recommendation": {
"modelCode": "18899",
"size": "44",
"displaySize": "44 EU",
"sizeSys": "EU",
"sizeSysGender": "",
"selectedSize": "44"
"displayString": "Recommended size for Johnny HasScan: 44 EU",
"personName": "Johnny HasScan"
},
"referenceData": //whole element, elementID, index, object, function etc.
}
The retailer receives a custom webview loader.html url which hosts Fitmate Size. The parameters are passed in as URIEncoded data. The mandatory and optional parameters are same as with metatag or SPA implementations.
Example url:
https://safesizepublic.s3.eu-west-1.amazonaws.com/fmsize/web/dev/loaderSAFESIZEDEMO-c5e7f0ee-32ea-445a-b15e-6cf92d6cc19f.html?image=http://s3-eu-west-1.amazonaws.com/safesizepublic/data/pictures/models/863769-802__NIKE.001.jpg&name=Air%20Zoom&shoe_model=AirZoomElite9MA__NIKE&brand=NIKE
Instruction for communicating with Webview taken from here:
https://medium.com/@sreeharikv112/communication-from-webview-to-native-ios-android-app-6d842cefe02d
If the app won't open you could have localStorage disabled inside webview. Enable with the webview setDomStorageEnabled flag.
To receive data from webview, we will use the WKScriptMessageHandler protocol. This will help us to add script message handler from native with specific name of the WK message handler.
var mNativeToWebHandler : String = “SafesizeHandler”
mWebKitView.configuration.userContentController.add(self, name: mNativeToWebHandler)
After this, we can use the function userContentController to track specific handler.
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == mNativeToWebHandler {
//Received message from webview in native, process data
}
}
Now we need to have a function in the loader.html JS Script to send the data from HTML page. The name of the message handler will be the same as in webkit. This is provided by Safesize side.
function forwardEventToNative(event) {
window.webkit.messageHandlers.SafesizeHandler.postMessage(event);
}
To receive data from webview, we can create an interface, which will enable webview to connect to the native layer and pass data.
From native layer, create a class and replicate the following.
class SafesizeHandler() {
@JavascriptInterface
fun postMessage(message:String){
//Received message from webview in native, process data
}
}
While configuring web view, we need to set JavaScript interface as above class.
mWebViewComponent.settings.javaScriptEnabled = true
mWebViewComponent.addJavascriptInterface(SafesizeHandler(), "SafesizeHandler")
Now we need to have a function in the loader.html JS Script to send the data from HTML page. The name of the message handler will be the same as in webkit. This is provided by Safesize side.
function forwardEventToNative(event) {
SafesizeHandler.postMessage(event);
}