This document describes how to integrate seatmap HTML page (further "seatmap") into any website, Android or iOS mobile app. Plus, communication between the seatmap and a parent layer that embeds seatmap (further just "parent layer").
They say seats with letter F are CSS bug. I say it's a smoking area 😜
- Website integration
- Android integration
- iOS integration
- i18n internationalization
- Color themes
- Available communication message types
- Initialisation and workflow
This section explains how to integrate seatmap into a website, Android and iOS mobile apps.
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 = 'http://demo-seatmap.quicket.me/htmls/dev/htmls/1764.html';
document.body.appendChild(iframe);
⚠️ It's important to listen toloadevent, 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: {
flightGuid: '1ABC',
passengers: [
{
id: '1',
seatPreference: null,
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);
Seatmap shall be rendered using WebView.
WebView webview = new WebView(this);
setContentView(webview);
webview.loadUrl("1764.html?<b>platform=android</b>");
⚠️ It's important and required to include?platform=androidGET parameter into loadable url.
When WebView contents is loaded, it's possible to publish messages to the seatmap and receive messages back from it.
Publish message into the seatmap by executing public JavaScript function within WebView — web.sendMessage(message: string). Seatmap has this function available for you.
webView.evaluateJavascript("web.sendMessage(\'{\"type\":\"SYNC_PASSENGERS\",\"data\":{\"flightGuid\":\"1ABC\",\"passengers\":[{\"id\":\"1\",\"seatPreference\":null,\"seat\":null}]}}\');
To receive message from the seatmap add JavaScript interface into the seatmap. Normally, webView.addJavascriptInterface() native method can be used to inject and map Java interface inside JavaScript source code in a WebView. Interface shall contain handleSeatmapEvent() public method. Make sure the name used to expose the object in JavaScript is android.
Here's approximately how to add JavaScript interface into WebView:
import org.json.JSONObject;
…
class JsObject {
@JavascriptInterface
public void handleSeatmapEvent(String: serializedMessage) {
JSONObject message = new JSONObject(serializedMessage);
// `message`, matches `IMessage` standard interface with data received from the seatmap.
}
}
webView.addJavascriptInterface(new JsObject(), "android");Now when interface is available within JavaScript code inside WebView, seatmap is going to execute android.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.
Seatmap shall be rendered within WKWebView.
import WebKit
…
var webView = WKWebView()
var url = NSURL(string:"1764.html?<b>platform=ios</b>")
var req = NSURLRequest(URL:url)
webView!.loadRequest(req)
⚠️ It's important and required to include?platform=iosGET parameter into loadable url.
Publish message into the seatmap by executing public JavaScript function — web.sendMessage(message: string). Seatmap has this function available for you.
webView.evaluateJavaScript("web.sendMessage(\'{\"type\":\"SYNC_PASSENGERS\",\"data\":{\"flightGuid\":\"1ABC\",\"passengers\":[{\"id\":\"1\",\"seatPreference\":null,\"seat\":null}]}}\')
To receive message from the seatmap, follow this article.
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.
Seatmap is able to display labels (especially seat descriptions) in multiple languages:
- English
en,en-USoren_US - German
de,de-DEorde_DE - Russian
ru,ru-RUorru_RU - Chinese Simplified
cn,cn-CNorcn_CN - French
fr,fr-FRorfr_FR - Italian
it,it-ITorit_IT - Swedish
sv,sv-SVorsv_SV - Danish
da,da-DAorda_DA - Norwegian
no,no-NOorno_NO - Spanish
es,es-ESores_ES
Default language is English.
To enforce seatmap labels display in a specific language, include ?language= GET parameter into seatmap url/path rendered inside iframe, WebView or WKWebView.
When seatmap is integrated into a website:
iframe.src = 'http://demo-seatmap.quicket.me/htmls/dev/htmls/1764.html?language=ru_RU';
When integrated into Android mobile app:
webview.loadUrl("1764.html?platform=android&<b>language=ru_RU</b>");Or within iOS app:
var url = NSURL(string:"1764.html?platform=ios&<b>language=ru_RU</b>")
var req = NSURLRequest(URL:url)
webView!.loadRequest(req)
Seatmap styling could be changed using color themes. At the moment, we have 4 different color themes:
- default — 1764.html?colorTheme=default
- kayak — 1764.html?colorTheme=kayak
- momondo — 1764.html?colorTheme=momondo
- skyscanner — 1764.html?colorTheme=skyscanner
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 = 'http://demo-seatmap.quicket.me/htmls/dev/htmls/1764.html?<b>colorTheme=skyscanner</b>';When integrated into Android mobile app:
webview.loadUrl("1764.html?platform=android&<b>colorTheme=kayak</b>&language=cn_CN");Or within iOS app:
var url = NSURL(string:"1764.html?platform=ios&<b>colorTheme=momondo</b>&language=ru_RU")
var req = NSURLRequest(URL:url)
webView!.loadRequest(req)
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:
- DECK__SWITCH
- SEAT_AVAILABILITY_AVAILABLE
- SEAT_MAP__DATA
- SEAT_MAP__LOADED
- SYNC_PASSENGERS
- WANNA_CLOSE_SEATMAP
- WANNA_SEE_VR
- SEAT__JUMP_TO
- SEAT_MAP__SCROLL_TO
- SEAT__PRESSED
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;
platform: string; // "web" / "android" / "ios"
scrollLeftInPx: number;
scrollTopInPx: number;
widthInPx: number;
}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__LOADED"
}
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"
}
This message gets bubbled up to the parent layer when 360° icon is clicked inside the seatmap. Parent layer shall implement logic to display 360° panorama.
Interface describing data received by the parent layer:
interface IData {
fragment: string; // Filename of the aerial panorama photo
}Example of data parent layer receives via WANNA_SEE_VR message bubbled up from the embed seatmap:
{
"data": {
"fragment": "6N"
},
"type": "WANNA_SEE_VR"
}
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"
}
This message works both directions (bidirectional):
- When posted from parent layer, it enables passengers allocation within seatmap.
- Seatmap emits this message, when particular passenger picks the seat. Actually, every time when passenger picks its seat or preferences, seatmap emits this message with updated list of passengers.
Interfaces, describing data types:
interface IAllocatablePassengers {
readonly flightGuid: string;
readonly passengers: IPassenger[];
}
interface IPassenger {
readonly id: string;
seatPreference: ISeatPreference;
seat: ISeat;
}
interface ISeatPreference {
cabinSection: TCabinSection;
seatPosition: TSeatPosition;
seatLabel: string;
}
interface ISeat {
price: number;
seatLabel: string;
}
type TCabinSection = 'NOSE' | 'MIDDLE' | 'TAIL';
type TSeatPosition = 'WINDOW' | 'MIDDLE' | 'AISLE';Example of data when SYNC_PASSENGERS message is fired:
{
"data": {
"flightGuid": "1ABC",
"passengers": [
{
"id": "1",
"seatPreference": null,
"seat": null
},
{
"id": "2",
"seatPreference": null,
"seat": {
"price": 0,
"seatLabel": "12F"
}
},
{
"id": "3",
"seatPreference": {
"cabinSection": "TAIL",
"seatPosition": "WINDOW",
"seatLabel": "2A"
},
"seat": null
}
]
},
"type": "SYNC_PASSENGERS"
}Or even like this:
{
"data": {
"flightGuid": "1ABC",
"passengers": [
{ "id": "1" },
{ "id": "2" },
{ "id": "3" }
]
},
"type": "SYNC_PASSENGERS"
}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 preferences, passengers array shall contain 2 items.
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 {
errors: string;
results: IIncomingResult[];
}
interface IIncomingResult {
availableClasses: IIncomingClass[];
notAvailableClasses: IIncomingClass[];
}
interface IIncomingClass {
classCode: string;
seats: IIncomingSeat[];
}
interface IIncomingSeat {
currency: string;
label: string;
price: number;
}Example of data posted from parent layer to the seatmap via SEAT_AVAILABILITY_AVAILABLE message:
{
"data": {
"errors": "",
"results": [
{
"availableClasses": [
{
"classCode": "E",
"seats": [
{
"currency": "USD",
"label": "12E",
"price": 33
},
{
"currency": "USD",
"label": "12F",
"price": 13
}
]
}
]
}
]
},
"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.
{
"data": {
"errors": "",
"results": [
{
"availableClasses": [
{
"classCode": "E",
"seats": [
{
"currency": "USD",
"label": "*",
"price": 50
},
{
"currency": "USD",
"label": "12F",
"price": 75
}
]
}
]
}
]
},
"type": "SEAT_AVAILABILITY_AVAILABLE"
}
This event can be sent to embed seat map to switch plane deck. When event is sent field deckId is in priority. It should be numeric field equals 1 or 2. Currently there is only double-deck aircrafts exists. If deckId is not defined or not valid application attempts to select deck by seatLabel.
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, // this field is priority
"seatLabel": "14D"
},
"type": "SWITCH_DECK"
}
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. effect responsible for animation during jump. If ``
Data:
| Name | Type | Required | Default | Possible values |
|---|---|---|---|---|
| label | string | ✔️ | ||
| tooltip | boolean | ✖️ | false |
false, true |
| effect | string | ✖️ | "none" |
"none" "linear" "swing" |
| duration | integer | ✖️ | 400 |
Interfaces:
interface IEventSeatJumpTo {
readonly data: {
readonly label: string;
readonly tooltip?: boolean;
readonly effect?: string;
readonly duration?: number;
};
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"
}Motion scrolling to seat in half a second
{
"data": {
"label": "18E",
"tooltip": true,
"effect": "swing",
},
"type": "SEAT__JUMP_TO"
}Linear scroll speed to seed in 600 ms.
{
"data": {
"label": "18E",
"tooltip": true,
"effect": "linear",
"duration": 600
},
"type": "SEAT__JUMP_TO"
}
Since version 0.4.0.
This event post to parent layer information about scroll position changes.
Message has 250ms delay (debounce) after event happened.
If it's Airbus A380 build or color theme of URL parameter is set to &iflya380=true - no scroll changes will be made and only message will posted to parent layer.
Interfaces:
export interface IEventSeatMapScrollToData {
/**
* @description Element height to fit into view. If set and above zero, height might be important.
* @default 0
* @readonly
* @type number
*/
readonly height?: number;
/**
* @description Element width to fit into view. If set and above zero, width might be important.
* @default 0
* @readonly
* @type number
*/
readonly width?: number;
/**
* @description Vertical position of the scroll bar that should be set
* @readonly
* @type number
*/
readonly x: number;
/**
* @description Horizontal position of the scroll bar that should be set
* @readonly
* @type number
*/
readonly y: number;
}
export interface IEventSeatMapScrollTo extends IEvent {
readonly data: IEventSeatMapScrollToData;
readonly type: MessageType;
}Example of data posted to parent layer to the seat map via SEAT_MAP__SCROLL_TO message:
{
"data": {
"x": 138.15,
"y": 4074
},
"type": "SEAT_MAP__SCROLL_TO"
}{
"data": {
"height": 292,
"width": 940.890625,
"x": 0,
"y": 4074
},
"type": "SEAT_MAP__SCROLL_TO"
}
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"
}
-
When seatmap is rendered and communication is set up, seatmap will be optionally waiting for
SEAT_AVAILABILITY_AVAILABLEandSYNC_PASSENGERSmessages. After receiving first message, seatmap is going to disable some seats, keeping others enabled. When second message arrives, seatmap is going unlock user with an opportunity to pick seat for reservation for every given passenger. -
Every time user picks particular seat for reservation, seatmap emits
SYNC_PASSENGERSmessage back to the parent layer. Message contains information with picked seat for every initialised passenger. -
If
SYNC_PASSENGERSmessage arrived, seatmap displays horizontal bar at the top. It contains ← icon (back button). When clicked, seatmap emitsWANNA_CLOSE_SEATMAPmessage up to the parent layer, signalling user wishes to close the seatmap and return back to the previous view. -
Some seatmaps are going to contain 360° dots and VR buttons within seat tooltip. When user clicks these, seatmap emits
WANNA_SEE_VRmessage.
