Custom
Follow the documentation below to integrate the Roof-Calc calculator into your custom website. This guide is suitable for use with any e-commerce website/environment which is not already supported by a prebuilt Roof-Calc integration. A properly implemented Roof-Calc integrations will allow users to generate "Bill of Material" quotes for their roofs through the calculator interface, and have the required products added directly to their basket on a per-project basis.
For the purposes of this guide, code snippets have been included in vanilla JS since this a browser-native language. Examples will have to be converted and/or extended for use within your specific environment.
Requirements
To create a custom Roof-Calc integration for your website, you MUST have the following:
- An e-commerce environment capable of handling decimal product quantities required for quoting and selling membrane products.
- Ability to modify website code or add your own scripts in order to create the elements required to add Roof-Calc to your website and respond to various events.
- Ability to call the Roof-Calc Management REST API (see documentation here), either periodically or in response to various events, in order to keep the product and quote data used by Roof-Calc up-to-date.
- A fully configured merchant account with:
- All of the required products and pricing configured to match your custom website.
- Calculator correctly configured to show both pricing details and add to basket functionality.
If you are having trouble meeting these requirements, please contact us by emailing [email protected].
Setup
To get the Roof-Calc calculator added to your custom website on a dedicated page as recommended, follow these steps with reference to the sections below:
- Add the Roof-Calc iframe to your page and add appropriate styling to ensure that the calculator renders correctly on all displays (see more).
- Listen for the various messages dispatched from Roof-Calc iframe and respond accordingly (see more).
- Add products to basket from line items specified by calculator (see more).
- Use the Roof-Calc Management REST API to keep Roof-Calc quotes and products updated (see more).
If you are having trouble setting up Roof-Calc on your custom website, please contact us by emailing [email protected].
Adding Iframe to Page
Roof-Calc is added to a page via an iframe element. The Roof-Calc URL is https://app.roof-calc.co.uk/?merchantId=<YOUR_MERCHANT_ID_HERE>. All query parameters in the parent window URL should be duplicated and passed to Roof-Calc in its iframe element's query parameters (see here for more details). See below for an example implementation; adding appropriate styles for the Roof-Calc iframe/container is environment-specific and beyond the scope of this guide.
<div
id="roof-calc"
data-merchant-ids=<YOUR_MERCHANT_ID_HERE>
data-scroll-on-load=true
data-redirect-url=<YOUR_BASKET_URL_HERE>
></div>
// Declare global variables.
const PUBLIC_ID_LIST_REGEX = /^(([A-Za-z]{4}(-|,|$)){2}){1,}(?<!,)$/
const ROOF_CALC_APP_URL = "https://app.roof-calc.co.uk"
/**
* Listener for messages posted from iframe to parent window, resolving
* based on message action. This listener is primarily responsible for
* adding to basket at the end of the calculator.
* @param {any} event Message event posted from Roof-Calc iframe.
*/
const iframeMessageListener = async (/** @type {any} */ event) => {
const action = event.data.action
if (action === "MOBILE_MEASUREMENT_INPUT_CLOSED") {
// Scroll back to top of iframe container when mobile input closed.
const iframeContainer = document.getElementById("roof-calc")
iframeContainer?.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
})
}
else if (action === "MOBILE_EMULATE_SCROLL") {
// Emulate mobile scroll of parent page from actions within iframe.
const top = event.data.top
window.parent.scrollBy({ top })
}
else if (action === "ADD_TO_BASKET") {
// Add products specified by Roof-Calc to the user's basket.
const { quoteId, lineItems } = event.data
for (const { uuid, sku, quantity, price } of lineItems) {
// Specific logic to add product to basket by SKU and QTY.
// ...
}
// Redirect or reload window based on value set in dataset. Note
// that in the case of reload, page is redirected with *all* query
// params removed to prevent repeated actions triggered by query.
const iframeContainer = document.getElementById("roof-calc")
const { redirectUrl } = iframeContainer.dataset
const { origin, pathname } = window.location
redirectUrl
? window.location.replace(redirectUrl)
: window.location.replace(origin + pathname)
}
}
/**
* Start Roof-Calc flat roofing calculator application interface with config
* details corresponding to this website's merchant account:
* - Merchant ID(s) inferred from dataset values in container.
* - Relevant query parameters from parent window passed to iframe.
*/
const startRoofCalc = () => {
// Fetch target iframe container and parse dataset values set in container
// block. End execution if iframe container not found.
const iframeContainer = document.getElementById("roof-calc")
if (!iframeContainer) { return }
let { merchantIds, scrollOnLoad } = iframeContainer.dataset
// Sanitize values fetched from container dataset.
if (!merchantIds?.match(PUBLIC_ID_LIST_REGEX)) { merchantIds = "" }
// Create iframe element pointing to Roof-Calc app base url, passing
// through any search parameters found in parent window location, and
// adding query parameters inferred from iframe container dataset.
const searchParams = new URLSearchParams(window.location.search)
if (merchantIds) {
searchParams.set(
merchantIds.match(/,/) ? "merchantIds" : "merchantId",
merchantIds
)
}
const iframe = document.createElement("iframe")
iframe.src = `${ROOF_CALC_APP_URL}/?${searchParams.toString()}`
// Set iframe to expand to fill available space in parent container.
// Parent container will be served by plugin as a div with id
// "roof-calc", and should be styled as required to fill the desired
// part of the page where the calculator should be.
iframe.style.width = "100%"
iframe.style.height = "100%"
// Add iframe message event listener and mount iframe to document in
// target container.
window.addEventListener("message", iframeMessageListener, true)
iframeContainer.append(iframe)
// Automatically scroll calculator interface into view if enabled by
// flag in dataset.
if (scrollOnLoad === "true") {
const iframeContainer = document.getElementById("roof-calc")
setTimeout(() => {
iframeContainer?.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
})
}, 600)
}
}
startRoofCalc()
Query Parameters
Roof-Calc responds to various query parameters passed from the parent window URL to the iframe element's URL. Specifically adding to basket from a saved quote sent to the user's email relies on passing parameters to Roof-Calc via query parameters in the parent window URL. For this reason, any integration MUST pass all window URL query parameters to the iframe via the query parameters in the iframe element's URL.
Iframe Events
Roof-Calc dispatches various messages to the parent window. All messages set an action key on the dispatched event's data object; possible values of the action key may be referenced below. For more details on the ADD_TO_BASKET action, see here.
CAUTION: Not all possible actions are referenced below, so any event listener for messages dispatched by the Roof-Calc iframe must NOT implement a default behaviour, and should instead always EXACTLY match the value of action.
MOBILE_MEASUREMENT_INPUT_CLOSED
/**
* Mobile measurement input close event is triggered when the user closes an
* input on mobile. Client should respond by ensuring that calculator is
* correctly scrolled back in to view if this behaviour is not native.
* @typedef {object} MobileMeasurementInputClosedEvent
* @property {string} action - Event action name.
*/
MOBILE_EMULATE_SCROLL
/**
* Mobile emulate scroll action is triggered when user scrolls on an element
* inside Roof-Calc which captures the scroll event such as the deck canvas.
* Client should respond by emulating scroll such that the page behaves as
* expected for the user.
* @typedef {object} MobileEmulateScrollEvent
* @property {string} action - Event action name.
* @property {number} top - Number of pixels to scroll by.
*/
ADD_TO_BASKET
/**
* Add to basket action is triggered at the end of the Roof-Calc interface when
* the user clicks the "Add to Basket" button on the bill of materials page.
* Client should respond by adding required line items to the user's basket and
* optionally redirecting the user.
* @typedef {object} AddToBasketEvent
* @property {string} action - Event action name.
* @property {string} quoteId - Quote public ID in format 123-ABC-890-DEF (12
* hexadecimal characters separated by hyphens).
* @property {LineItem[]} lineItems - Line items to add to user's basket.
*/
/**
* Line item typedef.
* @typedef {object} LineItem
* @property {string} uuid - UUID of product in Roof-Calc database.
* @property {string} sku - Merchant SKU code for product.
* @property {number} quantity - Quantity of product to add to basket (may be a
* decimal value).
* @property {number} price - Unit price of product quoted in calculator.
*/
Add to Basket
Roof-Calc dispatches message events to the parent window with the action key set to ADD_TO_BASKET in order add the specified products required for a given roof to the user's basket. Integrations MUST listen for this event and implement an add to basket function which is executed on the event's data object when triggered. See here for the data object's type information. Add to basket functions will be environment-specific and are beyond the scope of this guide.
Updating Quotes & Products
Roof-Calc relies on integrations to keep internal data up-to-date with the relevant data in a merchant's e-commerce environment.
Quotes
Integrations MAY update quote statuses when they are purchased by a user. The quote statuses saved and basket are handled automatically by Roof-Calc, however Roof-Calc has no way of telling when a user checks out a given quote in order to update the status to purchased. Periodic bulk updates (for instance using cron scheduling), or per-quote updates at basket checkout may be used to keep this value updated. See the API reference for more information.
Products
Integrations MUST update product pricing and stock data. Periodic bulk updates for product stock (for instance using cron scheduling), or per-product updates after a price change may be used to keep these values updated. See the API reference for more information.
A product may be set as dropshipped via the merchant's Roof-Calc dashboard or programmatically by using the API. In this case, product stock used by Roof-Calc for the purposes of generating quotes will reflect the supplier's stock of that product available for dropshipping instead of any stock set by the merchant.
Any SKU code changes or additions on the merchant's website must also be reflected correctly in Roof-Calc such that the merchant SKU stored against each supplier SKU in the Roof-Calc database matches the merchant SKU used for that supplier's product on the merchant's website. These values can be updated via the merchant's Roof-Calc dashboard or programmatically by using the API.
NOTE: Roof-Calc can only provide users with quotes based on the product pricing and stock data it has stored in its database for each product. Failure to keep this data up-to-date may result in quotes being specified with old pricing information or using products which are no longer in stock. In ALL cases it is the role of the merchant's basket and checkout to ensure that users are presented with the correct pricing information and to prevent users from purchasing out-of-stock products, even if specified to them by the Roof-Calc calculator. Roof-Calc is intended as a materials specification tool, and its data MUST NOT be considered as a source of truth for product pricing or stock data.