The difficult task of changing your seat while buying a train ticket online
I'm living in Portugal now. The country has a good railway system and one of the transportation ways I like the most when I travel is the train, which makes it a great choice for me to move around.
I wanted to travel to the south of the country so I decided to book a ticket with the state-owned train company: Comboios de Portugal and here is where my adventure begins.
The searching and selection process is quite easy and friendly but when I had to pick my seat is when things got interesting: it doesn't work!
I refused to buy my ticket with the seat that you get assigned by default. I always like to choose one in which both of them are free, knowing that someone can buy the ticket for the seat next to me, but if I'm lucky nobody will get the one next to me and I will travel more comfortably.
As a developer myself, I opened one of the best tools that we have at our disposal when we are building a website: Chrome Developer Tools in the hope of being able to dig a bit into the code and see what the problem was. Luckily for me, the web's Javascript code doesn't have any kind of minification or obfuscation.
The first thing I did was to look at the click event listeners for the seats:
I see that there are two events listening to the click of this button (I'm not interested in all the others in jQuery or Bootstrap). This is the code of each one:
// href # no jump to top
$(".slide-seats a").on("click", function(event) {
event.preventDefault();
});
$(".slide-seats .item table a").on("click", function(event) {
pickSeat($(this).find("img").attr("data-id"));
});
Of the two I see, the one that catches my attention is the second one since it calls a function pickSeat()
function pickSeat(seat) {
if (changeable) {
var idx = getSeatIndex(seat);
isOver = false;
var stat = getStatus(seat);
if (!changing && idx != -1) {
changing = true;
toChangeIndex = idx;
seats[toChangeIndex] = seat;
img = $(".slide-seats .active").find("[data-id='" + seat + "']")[0];
if (img != null) {// select
var name = getImg(seat) + '.4';
img.src = prefix + name + suffix;
}
} else if (changing && isSameTrain(seats[toChangeIndex], seat)
&& (stat == '0' || stat == '2')
&& (idx == -1 || idx == toChangeIndex)) {
changing = false;
resetSeat(seats[toChangeIndex]);
img = $(".slide-seats .active").find("[data-id='" + seat + "']")[0];
if (img != null) {// selected
var name = getImg(seat) + '.2';
img.src = prefix + name + suffix;
}
seats[toChangeIndex] = seat;
toChangeIndex = -1;
}
}
}
Setting a breakpoint on the first line of this function, when I click on one of the entries I see that the changeable
variable is true
and it goes inside the conditional block. The first function called is getSeatIndex()
passing the seat as a parameter:
function getSeatIndex(id) {
var id2 = id.split(':')[0];
for (var x = 0; x < seats.length; x++) {
var id1 = seats[x].split(':')[0];
if (id1 == id2)
return x;
}
return -1;
}
If the seat is not found inside the array seats
this function will return -1
. As my intention is to enter inside the following condition in the main function pickSeat()
, when it is inside the function of getSeatIndex()
I manually add the seat that I want to select inside the seats
array:
When the execution of the code reaches the line if(!changing && idx! = -1) {
, since we have modified the function getSeatIndex()
the variable idx
has a value of 1
which is different from -1
and the variable changing
is still false
then it executes the code inside the conditional, which was what I was looking for.
When the execution of the pickSeat()
function finishes I can see how the color of the seat changes to a light gray:
After these steps, naive of me I was expecting to continue with the process having my new seat selected, but when I click on next I get this error message:
🤔 Looks like it was not going to be as easy as I expected.
This message is displayed once you click on Next route
(since it is a round trip ticket, I also have to select the seat for the return trip) so the next thing I do is to inspect the button. Like I did with the buttons on the seats I check the Event Listeners
section, but for this button, I find nothing interesting.
However, when looking at the HTML element I find that a function is being called when the button is clicked:
With no time to waste, I go back to the Javascript code to see what is happening inside this change()
function
function change() {
if (changing) {
AlertMessage(msg_select);
return false;
}
focusNo = true;
if (sameSeats())
ConfirmMessage(msg_not_changed, 'doChange()');
else
ConfirmMessage(msg_changed, 'doChange()');
return false;
}
function doChange() {
changeTripSeatsValue();
enableUnload();
document.getElementById('next').click();
}
The message I was seeing before was because the changing
variable has a value of true
, so inside the change()
function that first conditional is being executed showing the message and returning false
.
Just before this first if
I change the value to false
:
I finish the code execution with the hope that now I would be able to move on to the next step with my selected seat but ... no!
Now an error message appears on the console:
Something is broken now 😰.
I see that the error comes from the sameSeats()
function which is the one called within the change()
function in the second if
function sameSeats() {
for (var x = 0; x < seats.length; x++) {
var a1 = seats[x].split('_');
var s1 = a1[0] + "_" + a1[1] + "_" + a1[2];
var a2 = oldSeats[x].split('_');
var s2 = a2[0] + "_" + a2[1] + "_" + a2[2];
if (s2 != s1)
return false;
}
return true;
}
Seeing the code of this function and the error, seems clear what is happening. It is trying to use split
in one of the elements of the seats
array or the oldSeats
array, and possibly using an index outside the array. Since the for
loop goes from 0
until the size of the array seats
, I'm going to assume that this array is correct, so the error would be in the line
var a2 = oldSeats[x].split('_');
To confirm this I debug the code again.
If I add these two variables to the Watch section I can see how, indeed, my guess is correct:
With the breakpoint on the line 199
, as shown in the screenshot, I go to the console and type oldSeats.push('184_4_64')
so that the sameSeats()
function doesn't fail this time and returns false
.
Finally the doChange()
function is executed. This function is responsible for calling another one, which is the one that makes the actual change of the seat:
function changeTripSeatsValue() {
var newValue = '';
for (var x = 0; x < seats.length; x++) {
newValue = newValue + seats[x];
if(x != seats.length - 1) {
newValue = newValue + ';';
}
}
document.getElementById('tripSeats').value = newValue;
}
In this function, I put a breakpoint on the last line, which seems to be the important one since it is responsible for assigning the new seat as the value of an item with tripSeats
id (I guess it is an input
, possibly hidden, within a form
element).
When the debugger stops in this line, I see how newValue
has a slightly strange value assigned, so I decide to change it to the value of the seat I am trying to select, and by continuing with the execution of the code ... bingo!
I have finally been able to select my seat :)
Conclusions
My purpose with this post is to document how I do a JavaScript code debugging process using Google Chrome developer tools. I think it is an interesting and very important process for any front-end developer.
In this case in particular with the Comboios de Portugal website, using vanilla Javascript (and some jQuery) and with a fairly easy to follow code, it has been a fun task because it is a code that I haven't written. This makes it more interesting and I took it as a challenge.
The funniest thing comes now: after having done all this process and having been able to select my seat I have continued testing things on this website and at one point I saw this message:
In summary... the selection of seats works so that you have to click first on the seat that you have been assigned automatically and then click on one of the empty seats. And yes, doing so works perfectly 🤦🏽♂️
I may be very clumsy, but since no one likes to accept his clumsiness I began to check other pages where you can book train and plane tickets. It turns out that in all the ones I tried the selection of seats is done in the way that I was instinctively trying: you just need to click on the empty seat.
In my opinion, this is the friendliest or easiest way to do it and the one that a person will instinctively try. I think it's a big UX issue on this specific page.
Anyway, and despite the fact that my entire process to select the seat was not necessary, I had a lot of fun and I ended up being satisfied to have been able to achieve that "challenge".