Thursday 31 August 2023

Spot The Bug: The Case of the Unremovable Tag

Howdy, noble bug-hunters and huntresses. I hope your bug-hunting is going well.

Damn, these bugs!


For this episode of Spot The Bug, we will dive into jQuery. I know it's not exactly fashionable anymore, but what the heck...

What I was trying to do in the code below, was have this box where anything text I entered would just appear in there.

<!DOCTYPE html>
<html>
    <head>
        <title>Easter Egg Hunt</title>

        <style>
            .tags
            {
                width: 600px;
                height: 200px;
                border: 1px solid rgb(255, 200, 0);
                border-radius: 5px;
                margin-bottom: 5px;
                padding: 10px;
            }

            .tag
            {
                border: 1px solid rgb(255, 200, 0);
                border-radius: 10px;
                font-family: Verdana;
                font-size: 12px;
                display: inline-block;
                height: 1.5em;
                padding: 2px;    
                text-align: center;    
                margin-right: 1em;    
                margin-bottom: 1em;    
                cursor: pointer;
            }
        </style>

        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

        <script>
            $(document).ready(function()
            {
                $("#btnAddTag").click(function() { addTag(); });
                $(".tag").click(function() { removeTag($(this)) });
            });

            function addTag()
            {
                    var tagText = $("#txtTag").val();
                    var newTag = $("<div></div>");
                    newTag.addClass("tag");
                    newTag.html(tagText);
                    $(".tags").append(newTag);                
            }

            function removeTag(obj)
            {
                obj.remove();
            }
        </script>
    </head>

    <body>
        <div class="tags">

        </div>
        <input id="txtTag" maxlength="8"/><button id="btnAddTag">Add Tag</button>
    </body>
</html>


Like so...




...and so on...




...and so for.




What went wrong

Looks great so far, right? Well, the thing is, the tags were also supposed to remove themselves from the box if I clicked on them. But no matter how I did it, they stubbornly remained in the box like turds that just refuse to be flushed away.

Why it went wrong

It was all in this line. It worked just fine when I was using it on the btnAddTag button. But now it was supposed to work on all elements styled using tag. Why didn't it work?
$(".tag").click(function() { removeTag($(this)) });


Because, the simple fact of the matter was, at the time this line was run, no such element existed. No elements styled using tag had been created yet. They would only get created when the btnAddTag button was clicked.

How I fixed it

For cases like this, we need event delegation. We need to tell the system to apply that rule to all future instances of that element. So we change the code this way. First, apply it to the container instead. In this case, it is the div that is styled using tags.
$(".tags").click(function() { removeTag($(this)) });


Then instead of using the click() method, use the on() method.
$(".tags").on(function() { removeTag($(this)) });


Add "click" to ensure that this is the event that is watched for.
$(".tags").on("click", function() { removeTag($(this)) });


And add the class name tag as the next argument, to indicate that these are the elements to apply the callback to.
$(".tags").on("click", ".tag", function() { removeTag($(this)) });


For illustration purposes, I'm going to add a lot more tags.




Now see what happens when I click on "CSS"?




And "HTML"?




Moral of the story

The click() method is deprecated anyway. But jQuery event delegation is still a thing.

Also, when you're dealing with front-end DOM manipulation, these are things to watch out for.

$(bugs).remove(),
T___T

Friday 25 August 2023

A Comparison Between Tables and Divs (Part 2/2)

Divs have been the de facto standard for web layout for over a decade since the advent of CSS. The versatility of divs cannot be overstated, though it is possible (and alarmingly easy!) to overuse them.

Deep levels of nesting.

Any web developer who has ever run into front-end maintenance problems due to multiple levels of nested div tags, will know exactly what I mean. This, of course, naturally leads to the question: Can tables be nested too? The answer is yes, and those seriously disturbed individuals who have attempted this will doubtless live to regret such depravity.

Learning curve

Divs are usually used as containers for HTML elements and content (including other divs) and then styled using CSS. This can lead to a whole ton of nested divs if a web developer is not careful. Again, as with tables, any missing open or closing tags leads to malformed HTML, thus it is important to keep them as simple as possible. Unlike tables, this is actually possible for divs, since the minimum viable HTML for one div is a single div tag.

<div>Content</div>

The learning curve for divs by themselves isn't huge, though CSS is another matter entirely. There are tons of options that can drastically change the visuals of your site. In addition to learning about the CSS Box Model, a web developer also needs to understand the position property if fancier shenanigans are required.

Use cases

Web layout now predominantly uses divs for many reasons, chief of which is mobile layout. Div layouts are easily made to be scalable (due to CSS) and this a site can be easily fitted to whichever screen size is required, without needing to make a lot of changes.

Different devices and
screen sizes.

Divs can also be used for tabular layout with a few interesting options depending on the library used, but this also tends to complicate matters; and in my opinion, does not significantly have less problems than simply using table tags for such use cases.

SEO

The ability to separate styling from content is one of the greatest boons of div tags and CSS. This solves many problems that table-based layouts are prone to.

Crawling the web.

For example, when using div tags for layout, it is possible to position important content in the HTML for web crawlers to pick up first, while visually repositioning the content elsewhere or even hiding it. Not only is it possible, CSS makes it almost ridiculously easy.

Conclusion

Tables and divs both have their place. Divs are ubiquitious. Tables fulfill a smaller, but still vital, place in the HTML ecosystem. It is important to use both for their respective best use cases.

Your div-ine web developer,
T___T

Wednesday 23 August 2023

A Comparison Between Tables and Divs (Part 1/2)

With the birth of the Internet, there have been two HTML elements that have often been used to do the same job - namely, the table and div tag. In this blogpost, we will compare the two and their best use cases.

Tables are predominantly a grid-layout element that is best used to present data that can be presented in a spreadsheet. In fact, if you highlight a HTML table and copy it, you can then paste the table in a spreadsheet application.

Spreadsheet data.

Tables can have columns that span multiple rows, and rows that span multiple columns. In this manner, more complex data can be represented.

Learning curve

Tables are not a difficult concept to muster. They can, however, be heinously cumbersome to write, let alone maintain. The table tag does not function on its own; it at the very least requires tr and td elements. thead and tbody are largely optional.

<table>
    <tr>
        <td>Content</td>
        <td>Content</td>
        <td>Content</td>
        <td>Content</td>
    </tr>
</table>


Usage is simple enough even if one throws rowspan and colspan attributes into the mix. Enough repetition and a child could learn it. The real question is whether or not that child would want to.

The problem with too many tags is that if one closing or opening tag is left out, this leads to malformed HTML. And with the nature of tables, this happens all too easily.

Use cases

These days it's not even a contest, but there was a time when using tables for page layout was actually a thing. In some dark corners of the web, table-based layout, along with (gasp!) frames, can be found if one looks hard enough. These days, though, suggesting tables as a site layout would earn you some serious side-eye.

Tables were adequate for layout back when screen sizes did not vary so much. Fixed width-layouts could handily be created with table elements that had the border attribute set to 0. Still not ideal, and smacked of trying to fit a square peg in a round hole, but they worked in a pinch if you weren't planning to make frequent changes or do anything extremely fancy.

You're not fitting that in.

Times have changed, and table layouts for websites are almost completely a thing of the past. Yes, I said almost. Hey, some people enjoy suffering; there's no accounting for taste, is there?

No, tables have now been relegated to carrying out the task they were originally meant for - presenting grid data. And that's a task they perform like no other.

SEO

Now, web crawlers have no problem reading content from tables. However, since tables and content are intrinsically tied together and tabular data is read linearly, this presents a problem if the visual design of the site clashes with the SEO requirements.

For instance, say you have content that you want displayed at the top of a site. However, in terms of importance, some other information takes precedence and you want the web crawlers to read that first. That can't happen in a table element unless you replicate the information at the top of the table and then hide it visually (which is workable I guess, just a huge pain in the butt to maintain).

Less than ideal
for the blind.

Also because tabular data is linear in nature, content readers for the visually impaired would totally mess it up if they were reading table-based web layout.

Next

We examine the div tag.

Saturday 19 August 2023

The How And Why Of One-Hot Encoding

Categorical data in a dataset are labels, usually in the form of strings. As I've mentioned before in a previous blogpost, they can also be numbers, but even if that were the case, they are numbers with no numerical relation to each other.

This normally does not present a problem; however, when categorical data needs to be input into a Machine Learning model, they need to be converted into numeric data.

Can't ingest letters...

The reason for this is that Machine Learning models currently do not work very well, or at all, with non-numeric data. To these models, the numbers need to have some sort of numerical relation to each other (as in the case with quantities, dates and such), or consist of 1s or 0s. When using such Machine Learning models, One-Hot Encoding (OHE) can be a boon as it allows you to have categorical data in your dataset.

How it works

Let's first use a sample column called Gender. Here's the column and its data.

Gender
M
M
F
M
M
F
F


For categorical data, we split that particular column into a number of columns equal to the number of possible values for that column. Thus, for a column like Gender, you might have two columns. Unless, of course, it's somewhere like Canada, in which case you might have more. Don't ask me why; it's how the world rolls right now.

The columns are renamed according to their data value and the original name, so we might have Gender_M and Gender_F. We sort the new columns alphabetically, so Gender_F comes before Gender_M. And then we convert the data into 1s and 0s. 1 being true and 0 being false. Thus, if the data is "M", it will show up as 1 under Gender_M and 0 under Gender_F.

Gender_F Gender_M
0 1
0 1
1 0
0 1
0 1
1 0
1 0


For a column with even more possible values, let's think of something like Shirt_Size. We'll place it next to the previously converted values for better clarity.
Gender_F Gender_M Shirt_Size
0 1 L
0 1 L
1 0 M
0 1 S
0 1 XL
1 0 L
1 0 XS


Here is the conversion.
Gender_F Gender_M Shirt_Size_L Shirt_Size_M Shirt_Size_S Shirt_Size_XL Shirt_Size_XS
0 1
1
0
0 0 0
0 1 1
0
0 0 0
1 0 0 1
0
0 0
0 1 0 0 1
0
0
0 1 0
0 0 1
0
1 0 1
0
0 0 0
1 0 0 0 0 0 1


Why not One-hot Encoding

Yes, OHE, like all solutions, is not ideal for every situation. These are some of the less desirable outcomes it can produce.

If there are many possible values for a column (such as a color code), this can lead to an unreasonably large number of columns. Basically, too many predictors (or data points) in the dataset.

Too many columns.

And if among these generated columns, only one column is a 1 while the others are 0, this leads to sparse data.

All this can lead to increased difficulty in training the Data Learning model.

Another issue, related to the previous points, is that OHE produces additional columns (and thus increased space) without actually adding to the value of the data. The data still means the same thing; it only takes up more space now. Kind of like if instead of this nice, short, to-the-point blogpost, I encapsulated the same points in a five-page article.

Conclusion

OHE is not the only method of preparing data for a Machine Learning model. There are others, of course, and perhaps on another occasion I can present them. Till then...

Have a One-derful day,
T___T

Monday 14 August 2023

Web Tutorial: The Chameleon Site

In recent weeks, I came across this amusing story about eBay. Basically, it's about UI and UX, and how users react negatively to sudden changes in a system unless that change takes place very slowly, over time. In the story, such a change was brought about, to the site's background color.

This story amused me so much that I just had to write a bit of code to replicate this effect. It's not spectacular and accomplishes nothing really new, but let's dive into it anyway, eh?

We first have some standard website HTML. I generated the text using ChatGPT.
<!DOCTYPE html>
<html>
    <head>
        <title>Chameleon</title>

        <script>

        </script>
    </head>

    <body>
        <p>
            Welcome to ColorShift! We are passionate about the power of colors to transform and enhance your world. Whether you're looking to revamp your home decor, design a striking logo, or simply add a touch of vibrancy to your digital creations, we have you covered. Our expert team is dedicated to helping you explore the endless possibilities of color.
        </p>

        <p>
            Discover a kaleidoscope of hues, shades, and tones that will ignite your creativity. Dive into our extensive color palette and find the perfect combination that speaks to your unique style. From bold and energetic to calm and soothing, we offer a vast spectrum of options to suit any mood or aesthetic.
        </p>

        <p>
            Our intuitive color selection tool allows you to experiment with different combinations, ensuring you find the harmony you're seeking. Adjust brightness, saturation, and contrast to achieve just the right balance. Create stunning gradients or play with complementary and contrasting colors to make your designs truly stand out.
        </p>

        <p>
            Whether you're a seasoned designer or a novice enthusiast, our blog is a treasure trove of inspiration and guidance. Dive into articles that delve into the psychology of color, explore the latest trends, and learn invaluable tips and tricks to make your colors pop.
        </p>

        <p>
            At ColorShift, we understand that colors have the power to evoke emotions, convey messages, and make a lasting impression. Let us be your guide on this chromatic journey. Unleash your imagination, unleash the colors!
        </p>
    </body>
</html>


Within the script tag, we create the setBgColor() function. It has three parameters. The first is maxDays, which is an integer that specifies how many days the change is going to take place over. fromColor and toColor are objects that dictate the beginning color and the end state color, each of these containing the RGB values in integers, as properties.
<script>
      function setBgColor(maxDays, fromColor, toColor)
      {

      }

</script>


When the site loads, we want to run the function. Let's first set it at 100 days, from the color white to orange.
<body onload="setBgColor(100, {r:255, g:255, b:255}, {r:255, g:200, b:0})">


Back to the setBgColor() function, we begin by declaring body as the HTML body tag.
function setBgColor(maxDays, fromColor, toColor)
{
      var body = document.getElementsByTagName("body");
      body = body[0];

}


We then declare startDate as a new Date object, and as a test, set it to the first day of this year. For this code to work, startDate should be before today's date.
function setBgColor(maxDays, fromColor, toColor)
{
      var body = document.getElementsByTagName("body");
      body = body[0];

      var startDate = new Date();
      startDate.setFullYear(2023, 0, 1);

}


Now we declare currentDate as another Date object, and it's today's date. Next, we have ms, which we use to calculate the number of milliseconds between currentDate and startDate.
function setBgColor(maxDays, fromColor, toColor)
{
      var body = document.getElementsByTagName("body");
      body = body[0];

      var startDate = new Date();
      startDate.setFullYear(2023, 0, 1);

      var currentDate = new Date();
      var ms = currentDate.getTime() - startDate.getTime();

}


And then we do some math on ms to get the number of days, assigning it to the variable days. We use floor() because days might not be a whole number, and it would be easier if it was.
function setBgColor(maxDays, fromColor, toColor)
{
      var body = document.getElementsByTagName("body");
      body = body[0];

      var startDate = new Date();
      startDate.setFullYear(2023, 0, 1);

      var currentDate = new Date();
      var ms = currentDate.getTime() - startDate.getTime();
      var days = Math.floor(ms / 86400000);
}


We declare finalColor and set it to the value of toColor.
function setBgColor(maxDays, fromColor, toColor)
{
    var body = document.getElementsByTagName("body");
    body = body[0];

    var startDate = new Date();
    startDate.setFullYear(2023, 0, 1);

    var currentDate = new Date();
    var ms = currentDate.getTime() - startDate.getTime();
    var days = Math.floor(ms / 86400000);

    var finalColor = toColor;
}


And now we set the background color of body to use the r, g and b properties in finalColor.
function setBgColor(maxDays, fromColor, toColor)
{
    var body = document.getElementsByTagName("body");
    body = body[0];

    var startDate = new Date();
    startDate.setFullYear(2023, 0, 1);

    var currentDate = new Date();
    var ms = currentDate.getTime() - startDate.getTime();
    var days = Math.floor(ms / 86400000);

    var finalColor = toColor;

    body.style.backgroundColor = "rgb(" + finalColor.r + ", " + finalColor.g + ", " + finalColor.b + ")";
}


What do we do with days, then? Well, we use a conditional statement to check if days is less than maxDays. If so, we'll alter the value of finalColor (not right now, though) but if not, the background color of body will effectively be the current value of finalColor, which is toColor.
function setBgColor(maxDays, fromColor, toColor)
{
    var body = document.getElementsByTagName("body");
    body = body[0];

    var startDate = new Date();
    startDate.setFullYear(2023, 0, 1);

    var currentDate = new Date();
    var ms = currentDate.getTime() - startDate.getTime();
    var days = Math.floor(ms / 86400000);

    var finalColor = toColor;

    if (days < maxDays)
    {

    }


    body.style.backgroundColor = "rgb(" + finalColor.r + ", " + finalColor.g + ", " + finalColor.b + ")";
}


Since it's 3rd August and definitely more than 100 days (the value of maxDays), you can see the color of background is orange!




Now in the If block, declare ratio. It's the result of dividing days by maxDays, which will give you a value less than 1 because days is less than maxDays.
if (days < maxDays)
{
    var ratio = days / maxDays;
}


Next, we declare rDiff, which is the difference between the r property of toColor and the r property of fromColor.
if (days < maxDays)
{
    var ratio = days / maxDays;

    var rDiff = toColor.r - fromColor.r;
}


Same for gDiff and bDiff.
if (days < maxDays)
{
    var ratio = days / maxDays;

    var rDiff = toColor.r - fromColor.r;
    var gDiff = toColor.g - fromColor.g;
    var bDiff = toColor.b - fromColor.b;  
 
}


If rDiff is greater than 0 (which means there is a difference), we alter the r property of finalColor to be the r property of fromColor, plus the ratio-ed value of rDiff! So the closer days is to maxDays, the closer the r property of finalColor will be to the r property of toColor.
if (days < maxDays)
{
    var ratio = days / maxDays;

    var rDiff = toColor.r - fromColor.r;
    var gDiff = toColor.g - fromColor.g;
    var bDiff = toColor.b - fromColor.b;    

    if (rDiff != 0) finalColor.r = fromColor.r + (Math.floor(rDiff * ratio));
}


Yes, we can do the same for the g and b properties of finalColor.
if (days < maxDays)
{
    var ratio = days / maxDays;

    var rDiff = toColor.r - fromColor.r;
    var gDiff = toColor.g - fromColor.g;
    var bDiff = toColor.b - fromColor.b;    

    if (rDiff != 0) finalColor.r = fromColor.r + (Math.floor(rDiff * ratio));
    if (gDiff != 0) finalColor.g = fromColor.g + (Math.floor(gDiff * ratio));
    if (bDiff != 0) finalColor.b = fromColor.b + (Math.floor(bDiff * ratio));

}


Let's test this! Change the value here to an entire year.
<body onload="setBgColor(365, {r:255, g:255, b:255}, {r:255, g:200, b:0})">


It's now 3rd August. That's 212 days after 1st Jan, right? 212 over 365 is 0.5808219178082191, give or take. So the color should be 58% orange!




Change this to an even bigger number...
<body onload="setBgColor(3650, {r:255, g:255, b:255}, {r:255, g:200, b:0})">


And you see the color gets closer to the "original" color, white.




Conclusion

I hope the logic was clear. This web tutorial is a little light on functionality, but it was fun to do. Which is the reason why I do anything on this blog.

Don't fade away on me now!
T___T

Tuesday 8 August 2023

From Twitter To X?

It appears that Elon Musk has come up with yet another bizarre decision. Sometime last week, Twitter underwent a name and logo change. Now, name changes aren't anything new in the world we inhabit. Facebook became Meta. Bruce Jenner became Caitlyn Jenner. In 1993, Prince became... well, you get the idea. Nothing to blink at.

Bye bye, birdy.

What's surprising, at least to me, is the what. From an internationally known moniker, the app has changed to a relatively generic X. The cute and tasteful blue bird that has served as the app's logo for more than a decade, is now a stylized and, dare I say, uninteresting "X". Now, not being a tech billionaire and all, nobody really cares about my opinion, and nor should they. I assume Elon Musk knows what he's doing... because I really don't. And I'm not entirely sure anyone else does either. Of all changes to take place, this stands out as a strange choice.

They call it a rebranding, but it really feels more like a debranding.

While I've never been a Twitter user, Twitter was such a mainstay of cyberspace that it was impossible for it to not be on my radar in some fashion. That's how strong its branding was. To throw that all away for a new but not altogether exciting image, seems counter-intuitive.

Burning questions!

Elon Musk has stated his desire to evolve Twitter into some kind of "Everthing App", just like WeChat. Something like that would require strong brand recognition as a foundation. Unfortunately, we live in a world where many things already share the name "X". And some of these things are distinctively pornographic in nature. Is that the kind of recognition that Elon Musk is going for? Somehow I doubt it.

Twitter's presence in not just cyberspace, but also Internet culture at large, has resulted in certain words being added to the lexicon. "Tweeting" is a verb which essentially means posting a message on Twitter. Now with the name change, what other changes could we see? Will Tweeting now be known as X-ing? Sounds uncomfortably violent.

Let me just do some X-ing...

Finally, of all the icons that Elon Musk could have gone with, why this one? I called it "relatively generic" earlier. I was being kind. It's boring. It's tepid. It says nothing about what the platform does. This could be deliberate. Musk could have some big plans for the app, and perhaps this generic quality is meant to avoid painting the app into a corner.

In Conclusion

With Meta's Threads encountering a bit of a downturn in recent weeks, this looked like a good time for Twitter to recover. Looks like it's not to be.

Still, it's going to be interesting seeing the two platforms taking turns to flounder. Definitely way more interesting than seeing one platform dominate. Let's hope that this does not lead to Elon Musk going from X owner to ex-owner. (hur hur)

Kick some X, Elon Musk!
T___T


Thursday 3 August 2023

The Real Reason Behind Political Resignation

The month of July in 2023 was a busy period for Singapore politics. There was a hot mic incident, a corruption scandal and not one, but two extramarital affairs uncovered. It's the latter that I will be covering today, after I've had a while to mull over what happened. My friends from Malaysia, in particular, have been having a field day remarking that Singapore politics has become, for once, more dramatic than Malaysian politics.

Till death do us part?

The first couple were members of the People's Action Party (PAP) and the second couple were from opposition party Worker's Party (WP). All four have resigned from their roles since.

This being Singapore, while not as ridiculously extreme as the USA (not yet, at least) taking sides was par for the course. On Social Media, I saw plenty of amusingly vapid comments from party fanboys trying to defend their side while decrying the other side for pretty much the same thing. Gotta love partisan politics. eh?

While I'm of the opinion that comments on Social Media are largely made by people trying desperately to sound clever and failing miserably, some of these comments are worth dissecting, if only as an exercise in cold, hard logic.

The Issue of Competence

One common refrain was that these people should not have stepped down because having an extramarital affair is not illegal and does not affect their competence.

That is correct. The fact that they betrayed their marriage vows has no legal relevance. They were elevated to their positions due to intelligence, experience and professional qualifications. Extramarital affairs do not make them suddenly unintelligent (there's an off-color joke to be made about blood rushing from the brain to other parts of the body that would result in temporary stupidity, but I shall not go there) and incompetent.


Liking avocado on toast?

My job as a software developer demands that I solve business problems with tech. Whether or not I am a choirboy or a foul-mouthed, immoral SOB does not change the fact that I can do my job, and I can do it well. I don't suddenly become incapable of writing code just because I cheat on my wife, or like eating avocado on toast, or like scratching my bum in public. One is my professional life, and one is my personal life. These are separate matters.

But precisely because these are separate matters, it should not even be part of the discussion. It's a mystery to me why people keep on harping about how the affairs don't affect their ability to do their job. Of course they don't, and that's exactly why them stepping down had nothing to do with competence.

The Moral Component

Another common refrain was that of morality. It's hard to argue that an extramarital affair isn't immoral. What's more contentious is whether morality should be a factor in whether or not politicians step down from their role. Arguing that a politicians should have to resign because their conduct was not in keeping with your morals, is a slippery slope. How far do you want to take it? They can't be in office if they don't have believe in the same God you do? Or support the same sports team you do? Or generally believe in the same things you do?

Must our public servants
share your faith?

I would also venture to say that anyone looking towards politicians, of all people, to be their moral standard, may as well depend on monkeys to predict the weather. Thus, you can probably tell that I have very little regard for the argument that their conduct was immoral and that warranted their expulsion from politics.

Actually Good Reasons to Resign

We've covered why the issues of competence and morality are irrelevant. What, then, is relevant?

Well, let's say I'm an extremely stellar software developer (I'm not, but this is for argument's sake) who can bend machines to my will and make tech wizardry happen. Without question, I am professionally qualified for my job. But what if my behavior publicly and dramatically contradicts my company's brand or mission statement? If that were the case, I would be seen as a liability, and no amount of expertise in my field would save me from the axe.

While Singapore is largely a meritocracy, suitability for a position does not consist of only professional competence. You would not, for instance, ask a Muslim to peddle grilled pork even if he or she might be an exceptionally good cook.

Mmm, pork.

Similarly, Tan Chuan Jin, as Speaker of the House, was expected to conduct himself with impartiality during Parliamentary debates between PAP and WP. Such impartiality is called into question if the Speaker has an affair with one member of one side.

Cheng Li Hui, Leon Perera and Nicole Seah, on their part, rode on platforms such as family values, transparency and accountability on the way to success in the last General Elections. None of which will have aged well in the light of these scandals.

It's also worth mentioning that extramarital affairs, for the most part, are secret affairs. They're not something people are necessarily proud of. Thus, for politicians, or indeed, anyone in a position of power, to voluntarily put themselves in a position that they could be compromised via blackmail, is beyond the pale.

That is why they all had to go. They had become liabilities.

Final words

We all have biases and our personal feelings. However, where national interests are concerned, those feelings should be put aside. And when arguing for one side against the other, it's especially important that these are intelligent and logical arguments.

But this being Social Media, my expectations remain low.

By affair means or foul,
T___T