Seatmap integration and communication

This document describes how to integrate seatmap HTML page (further "seatmap") into any website, Flutter, Android or iOS mobile app. Also, communication between the seatmap and a parent layer that embeds seatmap (further just "parent layer").

 

Table of contents

 

i18n internationalization   🇱🇻   🇺🇸   🇩🇪   🇺🇦

Seatmap is able to display labels (especially seat descriptions) in multiple languages:

Default language is English.

To enforce seatmap labels display in a specific language, include ?language= or ?lang= GET parameter into seatmap url/path rendered inside iframe, WebView or WKWebView.

When seatmap is integrated into a website:

iframe.src = 'https://seatmaps.quicket.io/aircrafts/seatmap.htm?language=ru';

When integrated into Android mobile app:

webview.loadUrl("seatmap.htm?<b>language=ru</b>");

Or within iOS app:

var url = NSURL(string:"seatmap.htm?<b>language=ru</b>") var req = NSURLRequest(URL:url) webView!.loadRequest(req)

 

Color themes

Seatmap styling could be changed using color themes. At the moment, we have 5 different color themes:

⚠️  You can try to create own color theme here and export a JSON file - we can built-in it in next release.

To enforce seatmap use one of these color themes, include ?colorTheme= GET parameter into seatmap url/path rendered inside iframe, WebView or WKWebView.

When seatmap is integrated into a website:

iframe.src = 'https://seatmaps.quicket.io/aircrafts/seatmap.htm?<b>colorTheme=skyscanner</b>';

When integrated into Android mobile app:

webview.loadUrl("seatmap.htm?<b>colorTheme=kayak</b>&language=zh-cn");

Or within iOS app:

var url = NSURL(string:"seatmap.htm?<b>colorTheme=momondo</b>&language=ru") var req = NSURLRequest(URL:url) webView!.loadRequest(req)

 

Modes

There is an availability to hide seatbar in top of seatmap.

To enforce seatmap to hide seatbar, include ?seatbar=hide GET parameter into seatmap url/path rendered inside iframe, WebView or WKWebView.

When seatmap is integrated into a website:

iframe.src = 'https://seatmaps.quicket.io/aircrafts/seatmap.htm?<b>seatbar=hide</b>';

When integrated into Android mobile app:

webview.loadUrl("seatmap.htm?<b>seatbar=hide</b>&language=zh-cn");

Or within iOS app:

var url = NSURL(string:"seatmap.htm?<b>seatbar=hide</b>&language=ru") var req = NSURLRequest(URL:url) webView!.loadRequest(req)

Also there is an availability to set mesurement system of tooltip features.

To enforce tooltip features to show data in metric system, include ?measure=metric and to show data in imperial system, include ?measure=imperial parameter into seatmap url/path rendered inside iframe, WebView or WKWebView.

When seatmap is integrated into a website:

iframe.src = 'https://seatmaps.quicket.io/aircrafts/seatmap.htm?<b>measure=metric</b>';

When integrated into Android mobile app:

webview.loadUrl("seatmap.htm?<b>measure=metric</b>&language=zh-cn");

Or within iOS app:

var url = NSURL(string:"seatmap.htm?<b>measure=metric</b>&language=ru") var req = NSURLRequest(URL:url) webView!.loadRequest(req)

Seatmap doesn't force the values of tooltip features by default.

 

Initialisation and workflow

📢  Available communication message types

All messages follow standard IMessage interface that is used for bidirectional communication:

interface IMessage { readonly type: string; readonly data: {}; }

type represents one of available message types (see below) and data is custom to every message.

Seatmap is able to receive and emit the following messages:


 

SEAT_MAP__LOADED

This message fires up when content (DOM tree, images etc.) is loaded. It also provides height and width of document view.

Interface, describing data types:

interface IWebViewData { deviceDPI: number; devicePixelRatio: number; heightInPx: number; planeId: number; floorsNumber: number; scrollLeftInPx: number; scrollTopInPx: number; widthInPx: number; }

Example of data parent layer receives:

{ "data": { "deviceDPI": 288, "devicePixelRatio": 3, "heightInPx": 736, "planeId": 1719, "floorsNumber": 1 "scrollLeftInPx": 0, "scrollTopInPx": 1456, "widthInPx": 414 }, "type": "SEAT_MAP__LOADED" }

 

SEAT_MAP__DATA

This message works in both directions (request–response). It is similar to SEAT_MAP__LOADED except you can call it anytime.

Interface, describing data types:

interface IWebViewData { deviceDPI: number; devicePixelRatio: number; heightInPx: number; planeId: number; platform: string; scrollLeftInPx: number; scrollTopInPx: number; widthInPx: number; }

Example how to call it:

{ "data": {}, "type": "SEAT_MAP__DATA" }

Example of data parent layer receives:

{ "data": { "deviceDPI": 288, "devicePixelRatio": 3, "heightInPx": 736, "planeId": 1719, "platform": "ios", "scrollLeftInPx": 0, "scrollTopInPx": 1456, "widthInPx": 414 }, "type": "SEAT_MAP__DATA" }

 

WANNA_OPEN_PANO

Bubbles up to the parent layer when user wishes to open panorama. Parent layer shall implement logic to show panorama, because the seatmap won't do it automatically.

Useful if you want to prevent default behavior of the seatmap after the user clicks on the Seat panoramic view button.

To enable external pano viewer mode just pass the ?external_pano_viewer=true query parameter.

Interfaces, describing data types:

export interface IEventWannaOpenPano { readonly classLetter: string; readonly deckLevel: number; readonly fragment: string; readonly label: string; readonly type: string; readonly seat_pitch: string; readonly seat_width: string; readonly seat_recline: string; }

Example of data parent layer receives:

{ "data": { "classLetter": "E", "deckLevel": 1, "fragment": "234876", "label": "60B", "type": "Economy", "seat_pitch":"30-32\"", "seat_width":"17.5\"", "seat_recline":"3\"" }, "type": "WANNA_OPEN_PANO" }

Values of the seat_pitch / seat_width / seat_recline fields depends on current mesurement system and can be in imperial (inch) or metric (cm).


 

WANNA_CLOSE_SEATMAP

Bubbles up to the parent layer when user wishes to close the seatmap and get back to the previous screen. Parent layer shall implement logic to close seatmap, returning user back to the previous state.

Optionally, there are some controls within the seatmap, that lets user emit message with this message.

Example of data parent layer receives:

{ "data": {}, "type": "WANNA_CLOSE_SEATMAP" }

 

SYNC_PASSENGERS

This message works both directions (bidirectional):

Interfaces, describing data types:

interface IAllocatablePassengers { readonly passengers: IPassenger[]; } interface IPassenger { readonly id: string; seat: ISeat; passengerType?: TPassengerType; passengerLabel?: string; passengerColor?: string; } interface ISeat { price: number; seatLabel: string; } type TPassengerType = 'ADT' | 'CHD' | 'INF';

Example of data when SYNC_PASSENGERS message is fired:

{ "data": { "passengers": [ { "id": "1", "seat": null }, { "id": "2", "seat": { "price": 0, "seatLabel": "12F" } "passengerLabel": "Alex", "passengerColor": "brown", }, { "id": "3", "passengerType": "CHD", "seat": null "passengerLabel": "John Snow", "passengerColor": "#ccc", } ] }, "type": "SYNC_PASSENGERS" }

Or even like this:

{ "data": { "passengers": [ { "id": "1" }, { "id": "2" }, { "id": "3" } ] }, "type": "SYNC_PASSENGERS" }

seat, passengerType, passengerLabel and passengerColor are not required fields.

If you will not pass passengerType, passengerLabel and passengerColor fields the values will be set by default.

Please notice, that seatmap identifies how many passengers to allocate by a length of passengers array. Therefore, for example, to allocate 2 passengers without any predefined seat or its type, passengers array shall contain 2 items.


 

SEAT_ADDITIONAL_PROPS

This message allows to add additional props to seat tooltip.

Interfaces:

export interface IAdditionalProp { label: string; icon?: string; } export interface IIncomingAdditionalProps { label: string; props: IAdditionalProp[]; }

Example of data when SEAT_ADDITIONAL_PROPS message is fired:

{ data: { seats: [ { "label": "12B", "props": [{"label":"Clear air", "icon":null}, {"label":"USB plug", "icon":'power'}], }, { "label": "10A", "props": [{"label":"Clear air", "icon": null}, {"label":"Nice view", "icon":"dot"}], }, { "label": "18B", "props": [{"label":"Soft seat"}], }, ], }, type: "SEAT_ADDITIONAL_PROPS", }

You can pass property without icon field or icon equal null. In this case the icon will be "dot" by default.

There are 6 types of predefined icons for additional props:

There is availability to add your custom icons in PNG/SVG formats with 20x20 dimensions.


 

SEAT_AVAILABILITY_AVAILABLE

Parent layer is able to post this message to the embed seatmap, guiding seatmap to enable some seats for reservation, while disable others.

Interfaces, describing data types:

interface IIncomingAvailibility { results: IIncomingResult[]; } interface IIncomingResult { availableClasses: IIncomingClass[]; notAvailableClasses: IIncomingClass[]; } interface IIncomingClass { classCode: string; seats: IIncomingSeat[]; } interface IIncomingSeat { currency: string; label: string; price: number; onlyForPassengerType?: TPassengerType[]; } type TPassengerType = 'ADT' | 'CHD' | 'INF';

Example of data posted from parent layer to the seatmap via SEAT_AVAILABILITY_AVAILABLE message:

{ "data": { "results": [ { "availableClasses": [ { "classCode": "E", "seats": [ { "currency": "USD", "label": "12E", "price": 33 }, { "currency": "USD", "label": "12F", "price": 13, "onlyForPassengerType": ["CHD", "INF"], } ] } ] } ] }, "type": "SEAT_AVAILABILITY_AVAILABLE" }

You can pass seat with label "*". This label work like all selector for seats. In example below all seats with classCode equals "E" will be enabled with price of 50. However next seat configuration "12F" will override price and set it to 75. You can pass seat with price 0. Also you can pass seat without price field and it would be the same as pass it with 0 value.

{ "data": { "results": [ { "availableClasses": [ { "classCode": "E", "seats": [ { "currency": "USD", "label": "*", "price": 50, "onlyForPassengerType": ["ADT", "CHD"], }, { "currency": "USD", "label": "12F", "price": 75 } ] } ] } ] }, "type": "SEAT_AVAILABILITY_AVAILABLE" }

If onlyForPassengerType field is empty or doesn't exist, then it has no restrictions value by default.


 

DECK__SWITCH

This event can be sent to embed seat map to switch plane deck. When event is sent field seatLabel is in priority.Field deckId should be numeric and equals 1 or 2. Currently there is only double-deck aircrafts exists.

Interfaces:

interface IEventSwitchDeck { readonly data: { readonly deckId?: number; // 1 or 2 and only if plane has second deck readonly seatLabel?: string; }; readonly type: string; }

Example of data posted from parent layer to the seat map via DECK__SWITCH message:

{ "data": { "deckId": 2, "seatLabel": "14D" // this field is priority }, "type": "DECK__SWITCH" }

In case you don't pass seatLabel property you have to pass deckId.


 

SEAT__JUMP_TO

The event is called from the outside from parent layer. This event switch view to plane deck on which seat is located. Then try to scroll (jump) possibly close to the seat trying to fit into center of view. The seat may not be scrolled completely to the center depending on the layout or scroll position. If tooltip option is passed reservation window will be shown and view will be scrolled to fit it in.

Data:

Name Type Required Default Possible values
label string ✔️
tooltip boolean ✖️ false false, true

Interfaces:

interface IEventSeatJumpTo { readonly data: { readonly label: string; readonly tooltip?: boolean; }; readonly type: string; }

Example of data posted from parent layer to the seat map via SEAT__JUMP_TO message:

Instant jump to seat

{ "data": { "label": "18E", "tooltip": true, }, "type": "SEAT__JUMP_TO" }

 

SEAT__PRESSED

Interfaces:

export interface IEventSeatPressed { readonly classLetter: string; readonly deckLevel: number; readonly fragment: string; readonly label: string; readonly type: string; readonly x: number; readonly y: number; }

Example of data posted to parent layer to the seat map via SEAT__PRESSED message:

{ "data": { "classLetter": "E", "deckLevel": 1, "fragment": "234876", "label": "60B", "type": "Economy", "x": 55.8, "y": 1108.88 }, "type": "SEAT__PRESSED" }

 

APPLY_COLOR_THEME

This event can be sent to embed seat map to change theme colors.

Interfaces:

export interface IEventApplyColorTheme { readonly aircraft__background-color?: string, readonly bulk-base__fill?: string, readonly bulk-cut__fill?: string, readonly floor__fill?: string, readonly turbine-tail-wing__stroke?: string, readonly cockpit-door__fill?: string, readonly cockpit-door__stroke?: string, readonly cockpit-windows__fill?: string, readonly fuselage__fill?: string, readonly fuselage-cut__fill?: string, readonly fuselage-separator__fill?: none, readonly fuselage-separator__stroke?: string, readonly icon__bulk__fill?: string, readonly seatbar__background-color?: string, readonly seatbar-guests__color?: string, readonly seatbar-seats__color?: string, readonly seatbar-separator__background-color?: string, readonly seatbar-icon__background-color?: string, readonly seatmap-seatnumber__color?: string, readonly tooltip-arrow__border-color?: string, readonly tooltip-button__color?: string, readonly tooltip-button__border-color?: string, readonly tooltip-button__background-color?: currentColor, readonly order-button__color?: string, readonly order-button__border-color?: string, readonly order-button__background-color?: string, readonly tooltip-feature-label__color?: string, readonly tooltip-feature-svg__fill?: string, readonly tooltip-feature-plus-svg__fill?: string, readonly tooltip-feature-minus-svg__fill?: string, readonly tooltip-feature-svg__stroke?: string, readonly tooltip-header__color?: string, readonly tooltip-unit__border-color?: string, readonly tooltip-unit__background-color?: string, readonly tooltip-unit__color?: string, readonly tooltip-unit-svg__fill?: string, readonly tooltip-vr-icon__fill?: string, readonly tooltip-wrapper__background-color?: string, }

Example of data posted to parent layer to the seat map via APPLY_COLOR_THEME message:

{ "data": { "comp-plane__background-color": "rgb(185, 186, 186)", "plane-vector_carpet__fill": "rgb(255, 255, 255)", "cockpit-door__stroke": "#ccc", "cockpit-windows__fill": "rgb(255, 255, 255)", "tooltip-button-icon__fill": "currentColor", "tooltip-button-icon__color": "rgb(189, 191, 190)", "tooltip-feature-label__color": "aqua", "tooltip-feature-svg__fill": "rgb(189, 191, 190)", "tooltip-feature-svg__stroke": "red", }, "type": "APPLY_COLOR_THEME" }

The value should be string in HEX / RGB(RGBA) / HSL or NAMED-COLOR format.

There are 5 properties which can be only predifined in the built-in theme :

⚠️  In this case, they can be configured using color theme editor and export a JSON file - we can built-in it in next release.


 

CUSTOMIZE_SCROLLBAR

Interfaces:

export interface IEventCustomizeScrollbar { readonly size?: string; readonly thumbColor?: string; readonly thumbBorderRadius?: string; readonly trackColor?: string; }

Example of data posted to parent layer to the seat map via CUSTOMIZE_SCROLLBAR message:

{ "data": { "size": '10px', "thumbColor": "rgb(255, 255, 255)", "thumbBorderRadius": "5px", "trackColor": "#fff", }, "type": "SEAT__PRESSED" }

The size value should be string in px / em / rem and other CSS-units format. By default - "auto". The thumbBorderRadius value should be string in px / em / rem / % and other CSS-units format. By default - "none". The thumbColor, trackColor values should be string in HEX / RGB(RGBA) / HSL or NAMED-COLOR format. By default - "#a8a8a8" and "#f1f1f1".

For Firefox browser are available only thumbColor, trackColor and size parameters . In this case if you pass size value less than "8px" it will be thin. In other case it will be autosized as default browser scrollbar size.

For hiding scrollbar just pass size value with "0px".


   

Integration

This section explains how to integrate seatmap into a website, Flutter, Android and iOS mobile apps.

 

Website integration

Embed seatmap into HTML page via <iframe>:

const iframe = document.createElement('iframe'); iframe.addAttribute('frameborder', '0'); iframe.addAttribute('marginheight', '0'); iframe.addAttribute('marginwidth', '0'); iframe.addAttribute('scrolling', 'yes'); iframe.id = 'seatmap'; iframe.addEventListener('load', handleSeatmapLoad, false); iframe.src = 'https://seatmaps.quicket.io/aircrafts/seatmap.htm'; document.body.appendChild(iframe);

⚠️  It's important to listen to load event, ensuring iframe contents is loaded before publishing messages to it.

  When iframe contents is loaded, publish message down to the embed seatmap via postMessage() function:

function handleSeatmapLoad(e: BrowserEvent) { const targetIframe = e.currentTarget as HTMLIframeElement; const targetIframeWindow = targetIframe.contentWindow; const message = { // Shall match `IMessage` standard interface. type: 'SYNC_PASSENGERS', data: { passengers: [ { id: '1', seat: null, }, ], }, }; targetIframeWindow.postMessage(JSON.stringify(message), '*'); }

  Subscribe to message emitted by the seatmap:

function handleMessage(e: MessageEvent): void { const message = JSON.parse(e.data); // `message`, matches `IMessage` standard interface with data received from the seatmap. } window.addEventListener('message', handleMessage, false);

 

Flutter integration

Seatmap shall be rendered using WebView.

import 'package:flutter_inappwebview/flutter_inappwebview.dart'; ... InAppWebView( initialOptions: options, onWebViewCreated: (controller) async { if (!Platform.isAndroid || await AndroidWebViewFeature.isFeatureSupported(AndroidWebViewFeature.WEB_MESSAGE_LISTENER)) { await controller.addWebMessageListener(WebMessageListener(jsObjectName: 'mobile', onPostMessage: (message, uri, isMainFrame, proxy) {})); } await controller.loadUrl( urlRequest: URLRequest(url: Uri.parse('{url ?? widget.url}?seatbar=hide&external_pano_viewer=true'))); }, onLoadStop: (controller, url) async { _controller = controller; setState(() { _loaded = true; }); if (widget.seat != null) { if (!Platform.isAndroid || await AndroidWebViewFeature.isFeatureSupported(AndroidWebViewFeature.WEB_MESSAGE_LISTENER)) { await controller.postWebMessage(message: WebMessage(data: WebViewMessageModel.jumpToSeat(widget.seat ?? '')), targetOrigin: Uri.parse("*")); } } }), ...

Android integration

Seatmap shall be rendered using WebView.

WebView webview = new WebView(this); setContentView(webview); // to listen post messages from seat map class JsObject { @JavascriptInterface fun postMessage(serializedMessage: String, transferList: String) { val type = JSONObject(serializedMessage).getString("type") when(type) { "SEAT_MAP__LOADED" -> {} // Do action } } } webView.addJavascriptInterface(JsObject(), "mobile") webview.loadUrl("seatmap_url.html"); // send message webView.postWebMessage(WebMessage("{\"data\":{\"label\":\"3A\",\"tooltip\":true},\"type\":\"SEAT__JUMP_TO\"}"), "*".toUri())

  When WebView contents is loaded host app will receive message SEAT_MAP__LOADED, it's possible to publish messages to the seatmap and receive messages back from it.

Now when interface is available within JavaScript code inside WebView, seatmap is going to execute mobile.handleSeatmapEvent(serializedMessage) method to send messages up to Android. You shall be able to receive every incoming message and build necessary business logic to handle it.

 

iOS integration

Seatmap shall be rendered within WKWebView.

import WebKit … var webView = WKWebView() var url = NSURL(string:"seatmap_url.html") var req = NSURLRequest(URL:url) webView!.loadRequest(req)

  Publish message into the seatmap by executing public JavaScript functionweb.sendMessage(message: string). Seatmap has this function available for you.

webView.evaluateJavaScript("parent.sendMessage(\'{\"type\":\"SYNC_PASSENGERS\",\"data\":{\"passengers\":[{\"id\":\"1\",\"seat\":null}]}}\')

  In general, WKWebView automatically injects webkit.messageHandlers.callbackHandler.postMessage() method into the seatmap. This method connects WKWebView contents and Swift native source code. Seatmap is going to execute this method to send messages up to iOS. You shall be able to build necessary business logic to handle incoming messages.

postMessage(serializedMessage) is going to receive the only parameter, that is serialised JSON object matching IMessage standard interface. Learn to handle incoming messages and implement necessary business logic.