New seatmap documentation

Install the required package:

npm i btrz-seatmaps

This will install the code required to draw a seatmap and communicate the changes on seats in real-time if required. If the site where you want to plug this is bundling or minifying script files you can simply include node_modules/btrz-seatmaps/lib/seatmap-section.js into the process and SeatmapSection class will be able to be called from there. Opening the browser console and running new SeatmapSection("seatmap", {}).draw(); where "seatmap" is the id of any div container, will help to easily check the code needed is in place and accessible.

Or it's possible to import and run it this way:
// Example
<!DOCTYPE html>
<html>
    <head>
        <script src="node_modules/btrz-seatmaps/lib/seatmap-section.js" type="text/javascript"></script>
        <link rel="stylesheet" href="btrz-jds.css" />
    </head>

    <body>

        <div id="seatmap" class="side-panel seatmap pointer-events-none user-select-none"></div>


        <script>
            new SeatmapSection("seatmap", {}).draw();
        </script>


    </body>
</html>

It will draw a seatmap section with a default fixed set of available seats and no facilities, into a "seatmap" div container. Next sections will show how to load it properly with real data.

Get the seatmap configuration and its capacity

Use the seatmaps availability api call to get the seatmap sections and seats information in order to draw the seatmap. This call will retrieve a list of sections per seatmap, and information about the number of seats, the way of labelling them, seat classes, seat fees, the different status of each seat, etc.

//Example (client is an axios instance)

return client({
      url: "https://api.betterez.com/inventory/seatmaps/{seatmapId}/available-seats/{routeId}/{scheduleId}/{manifestDate}",
      method: "get",
      params: {
        newdesign: true,
        originId: originStationId
        destinationId: destinationStationId,
        legFromIndex,
        legToIndex
      }
      headers: {
        'x-api-key': "{{x-api-key-value}}",
        'authorization': `Bearer ${jwtToken}`
      }
    });

Notice a "newdesign" query parameter is REQUIRED to call the mentioned api in order to work with the "new" design seatmaps. This is a boolean flag and if not present or false we'll keep working with "old" design seatmaps.
Replace the x-api-key-value and the jwtToken with proper values for your account. see here

This call will retrieve a json object similar to

// Example seatmap results:
{
    "seatmap": {
        "_id": "65b85d6ccdefd805046f7c08",
        "name": "MB PDD MIX 49",
        "capacity": 49,
        "sections": [
            {
                "_id": "65b85a9ca1a794050af6c043",
                "rowsEnumNoGaps": false,
                "seatsPerRowLeft": 2,
                "seatsPerRowRight": 2,
                "customSeats": [
                    {
                        "row": 5,
                        "col": 1,
                        "status": "available",
                        "label": "12"
                    },
                    {
                        "row": 3,
                        "col": 5,
                        "status": "available",
                        "label": "7",
                        "seatClass": "6555a94bdef8f0051a52b37b"
                    },
                ],
                "facilities": [
                    {
                        "type": "door",
                        "row": 4,
                        "col": 5,
                        "height": 2,
                        "width": 1,
                        "alignment": {
                            "key": 2,
                            "value": "Right"
                        }
                    },
                    {
                        "type": "stairway",
                        "row": 7,
                        "col": 4,
                        "height": 1,
                        "width": 2,
                        "label": "",
                        "orientation": {
                            "key": 1,
                            "value": "Horizontal"
                        }
                    },
                    {
                        "type": "driver",
                        "row": 1,
                        "col": 1,
                        "height": 1,
                        "width": 5,
                        "label": "",
                        "alignment": {
                            "key": 1,
                            "value": "Left"
                        }
                    },
                    {
                        "type": "gap",
                        "row": 3,
                        "col": 4,
                        "height": 1,
                        "width": 1
                    }
                ],
                "availableRows": "3",
                "availableCols": 5,
                "seatsWithStatus": [],
                "enumType": 1,
                "enumDir": 1,
                "startingSeatLabel": 1,
                "rowLabelType": 1,
                "seatLabelType": 1,
                "startingRowLabel": "-1",
                "showRowLabels": false,
                "lastRowNoGap": false,
                "name": "PISO 1",
                "capacity": 7,
                "rowLabelRange": ""
            }
        ],
     },
    "customSeats": [
      {
          "row": 3,
          "col": 5,
          "status": "unavailable",
          "female": true,
          "label": "7",
          "seatClass": "6555a94bdef8f0051a52b37b"
      },
      {
          "row": 2,
          "col": 5,
          "suggested": true,
          "label": "S",
          "seatClass": "6555a94bdef8f0051a52b37b",
          "sectionId": "65b85a9ca1a794050af6c043"
      },      
    ]
}

This endpoint will return information about every single seatmap section in order to draw the seats and facilities. Under customSeats array we'll find a list of booked seats (having "unavailable" status), seats marked as suggested or seats with particular gender information ("female" seats). This information has to be included into the customSeats property of the related section (notice those seats will have a sectionId property).

Create and identify the container element

Now that we have access to the seatmap code and the information to draw a section, we need to set the element where the seatmap will be rendered into

<div id="seatmap"></div>

Remember the wrapper id since it will be our entry point to render the seatmap section as a required parameter.

Let's draw a seatmap section

To draw a seatmap section we need to create an instance of a SeatmapSection class first, providing following parameters to the constructor:

containerId (String) The Id of the seatmap container. REQUIRED.
section (Object) An object containing information about the seat section. REQUIRED.
settings (Object) Additional settings for the seatmap. OPTIONAL.

// Example
const seatmap = new SeatmapSection(
    containerId: "seatmap",
    section,
    settings: {
      fees,
      seatClasses,
      labels,
      socketEvents,      
      events,
    }
);

seatmap.draw();

Once we have the instance created properly we can call the draw method in order to get our section up and running.

settings.fees

The list of seat fees which are associated to the seats of this particular seatmap section we are about to load. Here we have a sample of the way of getting them:

//Example (client is an axios instance)

return client({
      url: "https://api.betterez.com/inventory/seat-fees",
      method: "get",
      params: {
        providerId: account_id,
        disabled: false,
        currency
      }
      headers: {
        'x-api-key': "{{x-api-key-value}}",
        'authorization': `Bearer ${jwtToken}`
      }
    });

We'll use the retrieved data to fill the settings.fees property with and Array similar to this one:
[
  {
    "_id":"6552f5365fac28050ca386ae",
    "shortName":"SPL",
    "type":"%",
    "value":"25",
    "name":"SPL",
    "bgcolor":"#d4eaf0",
    "valueToDisplay":"25"
  },
  {
    "_id":"6552ec3a2e880a0512a27219",
    "shortName":"VIP",
    "type":"%",
    "value":"20",
    "name":"VIP",
    "bgcolor":"#CF9D9B",
    "valueToDisplay":"20"
  },
  {
    "_id":"652825f8a6babd05144d778d",
    "shortName":"bas",
    "type":"$",
    "value":"7.00",
    "name":"bas",
    "bgcolor":"#F24C40",
    "valueToDisplay":"7.00"
  }
]

Just _id, name and bgcolor are required.


settings.seatClasses

The list of seat classes which are associated to the seats of this particular seatmap section we are about to load. Here we have a sample of the way of getting them:

//Example (client is an axios instance)

return client({
      url: "https://api.betterez.com/inventory/seat-classes",
      method: "get",
      params: {
        providerId: account_id,
        disabled: false
      }
      headers: {
        'x-api-key': "{{x-api-key-value}}",
        'authorization': `Bearer ${jwtToken}`
      }
    });

We'll use the retrieved data to fill the settings.seatClasses property with and Array similar to this one:
[
  {
    "_id":"64a524df44e060052291d4f7",
    "name":"BATHROOM SEAT",
    "description":"BATHROOM SEAT",
    "bgcolor":"#40F28D",
    "color":"#232970",
    "value":"BATHROOM SEAT"
  },
  {
    "_id":"6555a94bdef8f0051a52b37b",
    "name":"VIP",
    "description":"VIP",
    "bgcolor":"#34def4",
    "color":"#F9DEB7",
    "value":"VIP"
  },
  {
    "_id":"6555b54d9aa8b005205aeadc",
    "name":"NORMAL",
    "description":"NORMAL",
    "bgcolor":"#40F28D",
    "value":"NORMAL"
  },
  {
    "_id":"65b889f2a1a794050af6c05f",
    "name":"BATHROOM SEAT 2",
    "description":"BATHROOM SEAT 2",
    "bgcolor":"#1b0391",
    "value":"BATHROOM SEAT 2"
  }
]

Just _id, name and bgcolor are required.

settings.labels

The list of lexicon values so the seatmap can manage different languages to show titles, labels, etc. We need to provide an object with following fixed properties, setting their values in the required language:

{
  "section":"Section",
  "row":"Row",
  "seat":"Seat",
  "status":"Status",
  "seatClass":"Seat class",
  "fee":"Fee",
  "female":"Female",
  "suggested":"Suggested",
  "accessible":"Accessible"
}



Live seatmaps configuration and code sample

Live seatmaps allow you to display in real time when users in other browsers select a seat in the same seatmap you are working with. To set a live seatmap we need to fill the socketEvents property of the settings parameter when calling the seatmap section constructor.

settings.socketEvents

An object that defines the socket communication settings and attaches to events for real-time updates of the seatmap.

scheduleId (String) The schedule Id.
tripId (String) The trip Id.
callbacks (Object) Functions which will be triggered for various seat-related or broadcast events. socketUrl (String) The socket URL.
ttlSec (Number) Seat life time in seconds.
idForLiveSeatmap (String) The Id for live seatmap: ${ROUTE_ID}${MANIFEST_ID}${MANIFEST_DATE_YYY-MM-DD}.
legFrom (Number)
legTo (Number)
accessTicket (String) Access token for the socket.

settings.socketEvents.callbacks

Here is the list of the events which can be attached

settings.socketEvents.callbacks.seatClicked it will be called when a user in this particular seatmap clicks or hits Enter over an available seat or over an already selected one. This callback should be used to select and/or unselect seats.

settings.socketEvents.callbacks.seatClicked = (seat) => {
  const seatId = `section-${seat.sectionId}-row-${seat.rowLabel}-seat-${seat.label}`;
  if (seatWasAlreadySelected(seat)) {
    //Relase the seat
    SeatmapSection.changeSeatDataProp({col:  seat.col, row: seat.row, sectionId: seat.sectionId}, "selected", "false");
    //Broadcast the event
    SeatmapSocket.pushEvent("seat:unselected", {col:  seat.col, row: seat.row, sectionId: seat.sectionId}, seatId);
  } else {
    //Mark the seat as selected
    SeatmapSection.changeSeatDataProp({col:  seat.col, row: seat.row, sectionId: seat.sectionId}, "selected", "true");
    //Broadcast the event
    SeatmapSocket.pushEvent("seat:selected", {col:  seat.col, row: seat.row, sectionId: seat.sectionId}, seatId);
  }
};



settings.socketEvents.callbacks.seatOver it will be called after the mouseover event over the seat in this particular seatmap.


settings.socketEvents.callbacks.seatOut it will be called after the mouseover event out the seat in this particular seatmap.


settings.socketEvents.callbacks.seatExpired this one will be called when a "sync:seats" event arrives from broadcasting to the live seatmap, announcing a seat concludes its time to live

settings.socketEvents.callbacks.seatExpired = (seatExpired) => {
  if (ifExpiredSeatWasSelectedInThisSeatmap(seatExpired)) {
    //Release the seat
    SeatmapSection.changeSeatDataProp({col:  seatExpired.col, row: seatExpired.row, sectionId: seatExpired.sectionId}, "selected", "false");
  }
  //Mark the expired seat as available and ready to navigate through keyboard
  SeatmapSection.changeSeatStatus({col:  seatExpired.col, row: seatExpired.row, sectionId: seatExpired.sectionId}, "available");
  SeatmapSection.changeSeatDataProp({col:  seatExpired.col, row: seatExpired.row, sectionId: seatExpired.sectionId}, "keynav", "true");  
};



settings.socketEvents.callbacks.seatmapJoin it will be called after a "sync:join" event arrives when the live seatmap successfully plugs into the broadcast socket telling which seats were already blocked in a per section basis

settings.socketEvents.callbacks.seatmapJoin = (seats, sectionId, customSeats) => {
  seats.forEach((s) => {
      if (ifSeatBelongsToThisSeatmapSection(s)) {
        SeatmapSection.changeSeatStatus({col:  s.seat.col, row: s.seat.row, sectionId: s.seat.sectionId}, "blocked");
      }
    }
  });
};



settings.socketEvents.callbacks.seatmapSeatSelected this one will be called when a "seat:selected" event arrives from broadcasting when a user in another browser selects a seat

settings.socketEvents.callbacks.seatmapSeatSelected = (seat) => {
  if (ifSeatBelongsToThisSeatmapSection(s)) {
    SeatmapSection.changeSeatStatus({col:  seat.col, row: seat.row, sectionId: seat.sectionId}, "blocked");
  }
};



settings.socketEvents.callbacks.seatmapSeatUnSelected this one will be called when a "seat:unselected" event arrives from broadcasting when a user in another browser unselects a seat

settings.socketEvents.callbacks.seatmapSeatUnSelected = (seat) => {
  if (ifSeatBelongsToThisSeatmapSection(s)) {
    SeatmapSection.changeSeatStatus({col:  seat.col, row: seat.row, sectionId: seat.sectionId}, "available");
    SeatmapSection.changeSeatDataProp({col:  seat.col, row: seat.row, sectionId: seat.sectionId}, "keynav", "true");
  }
};



settings.socketEvents.accessTicket

In order to get the access ticket do this on the server side so not to expose your jwtToken. Replace the x-api-key-value and the jwtToken with proper values for your account. see here

//client is an axios instance
return client({
      url: "https://api.betterez.com/seatmaps/access-ticket",
      method: "post",
      headers: {
        'x-api-key': "{{x-api-key-value}}",
        'authorization': `Bearer ${jwtToken}`
      }
    });

Here we have a sample of the way of building the socketEvents object when we want to get the live seatmap up and running:

// Example
  const socketEvents =  {
    scheduleId: "a-schedule-id-123",
    tripId: "a-trip-id-123",
    callbacks: {
      seatClicked: window.onSeatClicked,
      seatExpired: window.onSeatExpired,
      seatmapJoin: window.onSeatmapJoin,
      seatmapSeatSelected: window.onSeatmapSeatSelected,
      seatmapSeatUnSelected: window.onSeatmapSeatUnSelected
    },
    socketUrl: "ws://{{domain}}/seatmaps/socket",
    ttlSec: 180,
    accessTicket: "a-access-ticket-123",
    idForLiveSeatmap: `routeId_scheduleId_manifestDate`,
    legFrom: 6,
    legTo: 5
  }

Broadcast events

We can call a static method to broadcast expected events to tell everyone listening that a seat was selected or unselected:

// Example
  SeatmapSocket.pushEvent("seat:unselected", seat, seatId);
  SeatmapSocket.pushEvent("seat:selected", seat, seatId);

seat (Object) Having col, row and sectionId as REQUIRED properties
seatId (String) The unique seat identifier: section-${seat.sectionId}-row-${seat.rowLabel}-seat-${seat.label}

Events:

In case you don't need to work with a live seatmap you can still define a different set of events which will be called by the seatmap on a per seat basis.

settings.events:

These array of events can be defined using the following properties on each of them:
elementType (String) type of element to which the event applies to, meaning seats or a specific facility (gap, door, table, etc). If this one is not present an elementStatus is REQUIRED
elementStatus (Array) Status of the seat element to which the event will be applied to (available, unavailable, blocked, reserved). If this one is not present an elementType is REQUIRED
type (String) REQUIRED Type of event (click, blur, focus, mouseover, mouseout etc).
cb (Function) REQUIRED Callback function to be triggered when the event occurs.

// Example
settings.events = [
  {
    elementType: "seat",
    elementStatus: ["available"],
    type: "click",
    cb: function (evt, e, elem) {
      window.onSeatClicked(elem);
    }
  }
];

window.onSeatClicked = (elem) => {
  const seatId = `section-${seat.sectionId}-row-${seat.rowLabel}-seat-${seat.label}`;
  if (seatWasAlreadySelected(seat)) {
    //Mark the seat as selected
    SeatmapSection.changeSeatDataProp(seat, "selected", "false");
    //Broadcast the event
    SeatmapSocket.pushEvent("seat:unselected", seat, seatId);
  } else {
    //Mark the seat as selected
    SeatmapSection.changeSeatDataProp(seat, "selected", "true");
    //Broadcast the event
    SeatmapSocket.pushEvent("seat:selected", seat, seatId);
  }
};
  



SeatmapSection useful methods

Here you have an extra set of exposed methods and can be useful to work with the seatmap sections:

clearFocus

Remove focus of an element within the seatmap container.

// Example
  seatmap.clearFocus();

.

clearSelection

Remove selection of elements inside the container of the seatmap.

// Example
  seatmap.clearSelection();

.

changeSeatDataProp

Changes a property of an element within the seatmap.

// Example
  seatmap.changeSeatDataProp(seat, prop, value );

changeSeatStatus

Changes the status of an element within the seatmap.

// Example
  seatmap.changeSeatStatus(seat, status);

.

focus

Focuses on the seatmap container and the first available seat within it.

// Example
  seatmap.focus();

.

focusElement

Focuses on a specific element within the seatmap.

// Example
  seatmap.focusElement(elem);

.

focusOnNextSelected

Focuses on the next selected seat in the seat map. Requires the previous seat selected as a parameter.

// Example
  seatmap.focusOnNextSelected(previousSeatSelected);

.

getCapacity

Calculates the current capacity of the seatmap.

// Example
  seatmap.getCapacity();

.

onSeatClicked

Activated when a seat on the seat map is clicked.
Checks if the seat has been marked as 'blocked' and sends its data via socket events.

// Example
  seatmap.onSeatClicked(elem);

.

selectElement

Selects an element within the seatmap.

// Example
  seatmap.selectElement(elem);