Friday 10 May 2019

Web Tutorial: Lee Bee Wah Lau

Dr Lee Bee Wah is a Member of Parliament for Nee Soon and recently, she's been an Internet meme for the unintentionally hilarious speech she made in Parliament.


It was actually a pretty sanctimonious admonishment to the general public, and drew a fair amount of ire. On my part, I almost doubled over in mirth. Good old Sis Flower - always good for a laugh. This isn't the first time she's been an object of ridicule. She has been quite the sport over the years, providing us with several meme-able moments due to her flair for the dramatic and her penchant for hyperbole, not to mention a hide like a rhinoceros.

In fact, back in 2016, when Joseph Schooling won the Olympic gold, Lee Bee Wah made herself famous with the following remark.


"I'm glad I asked MINDEF to let elite male athletes defer their NS."


This served to look as though she was claiming credit for Schooling's gold medal, and drew flack all around. My friend Benjamin Broekhuizen (who henceforth shall be known as "Ben" because typing his full name is a right pain in the arse), after facepalming heartily, was moved to write some code to express his distaste for that remark. It was a quick and dirty job - PHP with a randomizer to echo the result from a list of grandoise and increasingly silly claims.


Editor's Note: Nobody is saying that Sis Flower actually said these things (other than the first quote), but we're satirizing this particular quote.

Nothing against Lee Bee Wah, but I was tickled when I saw that page. Lee Bee Wah Lau - a portmanteau of her name and the common Hokkien expression of dismay. And then my web developer's brain went to work and I started wondering - what if we didn't have to refresh the page to get a new quote? Maybe just a button to randomize the next quote?

One thing led to another. Simple JavaScript would do the trick... but what's the fun in that? I'm trying to learn ReactJS. Let's use that!

And that's how this web tutorial was born...

What we're doing

We will make use of Ben's content (which he has very kindly shared) and make a new version using ReactJS and front-end binding. Yes, it's a very trivial use of ReactJS, and Ben did comment that it was like using a flamethrower to fry a mosquito, but what the hell man, we all gotta start somewhere, ya dig?

Let's start with some HTML, and a container div with an id of quoteContainer. Also add a little attribution link to Ben. It was his idea.
<!DOCTYPE html>
<html>
    <head>
        <title>Lee Bee Wah Lau!</title>
        <style>

        </style>
    </head>
    <body>
            <div id="quoteContainer"></div>
            <small>
                Original concept by <a href="https://www.linkedin.com/in/bsbroekhuizen/" target="_blank">Benjamin Broekhuizen</a>.
            </small>
    </body>
</html>


Here, we'll add reference links to React, ReactDOM and Babel. It's actually not the recommended way to use ReactJS and would probably not handle large projects very well, but it's great for something of this size and far less intimidating since it follows the typical web development pattern of including the reference link in the HTML itself.
<!DOCTYPE html>
<html>
    <head>
        <title>Lee Bee Wah Lau!</title>
        <style>

        </style>

            <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
            <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
            <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
    </head>
    <body>
            <div id="quoteContainer"></div>
            <small>
                Original concept by <a href="https://www.linkedin.com/in/bsbroekhuizen/" target="_blank">Benjamin Broekhuizen</a>.
            </small>
    </body>
</html>


Note that the script tag's type attribute is "text/babel". This is relevant because we're using ReactJS, and therefore it requires the Babel to compile. In it, we first define the Quote class. It's a ReactJS component, so declare it as an extension of the react.component class. Note that the script tag is within the body tag itself for ReactJS.
<!DOCTYPE html>
<html>
    <head>
        <title>Lee Bee Wah Lau!</title>
        <style>

        </style>

            <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
            <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
            <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
    </head>
    <body>
            <div id="quoteContainer"></div>
            <small>
                Original concept by <a href="https://www.linkedin.com/in/bsbroekhuizen/" target="_blank">Benjamin Broekhuizen</a>.
            </small>

            <script type="text/babel">
            class Quote extends React.Component
            {

            }
            </script>
    </body>
</html>


Whatever's returned by the Quote component will be rendered into the quoteContainer div, so do this. ReactDOM's render() method will use Quote (here represented as a tag with no quotes), and the quoteContainer div as an object.
    <body>
            <div id="quoteContainer"></div>
            <small>
                Original concept by <a href="https://www.linkedin.com/in/bsbroekhuizen/" target="_blank">Benjamin Broekhuizen</a>.
            </small>

            <script type="text/babel">
            class Quote extends React.Component
            {

            }

            ReactDOM.render(<Quote />, document.getElementById("quoteContainer"));
            </script>
    </body>


Let's create the render() method of the Quote component.
            class Quote extends React.Component
            {
                render()
                {
              
                }
            }


First, let's use Ben's content array. This will be assigned to Quote's content property.
            class Quote extends React.Component
            {
                render()
                {
                    this.content =
                    [
                       "asked MINDEF to let elite male athletes defer their NS.",
                       "asked God to let there be light",
                       "asked my parents to conceive me",
                       "asked John F. Kennedy to put a man on the moon",
                       "asked Lee Kuan Yew to create modern Singapore from a fishing village",
                       "asked Frodo to destroy the One Ring",
                       "asked for more cowbell",
                       "asked Rosa Parks to sit in the front of the bus",
                       "asked Martin Luther King what he dreamt of",
                       "asked Phelps to take it easy, so Schooling could win and I could take the credit",
                       "asked Indonesia to clean up Batam",
                       "asked Barack Obama to run for President",
                       "asked Obi-Wan Kenobi to save Amidala and her twins",
                       "asked Snape to take care of Harry",
                       "asked Def Leppard to pour some sugar on me",
                       "asked the residents of Yishun to vote for me, so I could ask MINDEF to let elite male athletes defer their NS"
                    ];   
                }
            }


Then we return the result. It's a HTML block (again, with no quotes, because it's not a string) consisting of a div and a blockquote tag. Within the blockquote, we have a templated string. The value of the first element of the content array has been put in.
            class Quote extends React.Component
            {
                render()
                {
                this.content =
                    [
                       "asked MINDEF to let elite male athletes defer their NS.",
                       "asked God to let there be light",
                       "asked my parents to conceive me",
                       "asked John F. Kennedy to put a man on the moon",
                       "asked Lee Kuan Yew to create modern Singapore from a fishing village",
                       "asked Frodo to destroy the One Ring",
                       "asked for more cowbell",
                       "asked Rosa Parks to sit in the front of the bus",
                       "asked Martin Luther King what he dreamt of",
                       "asked Phelps to take it easy, so Schooling could win and I could take the credit",
                       "asked Indonesia to clean up Batam",
                       "asked Barack Obama to run for President",
                       "asked Obi-Wan Kenobi to save Amidala and her twins",
                       "asked Snape to take care of Harry",
                       "asked Def Leppard to pour some sugar on me",
                       "asked the residents of Yishun to vote for me, so I could ask MINDEF to let elite male athletes defer their NS"
                    ];

                    return (
                        <div>
                            <blockquote cite="http://www.teochewthunder.com">
                                I am glad I {this.content[0]}
                            </blockquote>
                        </div>
                    );    
                }
            }


There, you'll see the text rendered on the screen.


Now what?

Well, we've got so much content, it's a shame to let it go to waste. Ben's original concept was that you had to refresh the page in order to get a randomized quote. Let's do that with a button instead. In the button's onclick event, we'll fire off the Quote component's changeQuote() method (which we haven't written yet, hold your damn horses!).
                    return (
                        <div>
                            <blockquote cite="http://www.teochewthunder.com">
                                I am glad I {this.content[0]}
                            </blockquote>

                            <p>
                            <button onClick={this.changeQuote}> More >> </button>
                            </p>
                        </div>
                    );


There, now we have a button.


We will now write the changeQuote() method. Before that happens, since we will be playing with state changes, we need a constructor for the Quote component. props will be passed in as an argument, but since there are no properties passed into the render() method, we don't really need that.
            class Quote extends React.Component
            {
                constructor(props)
                {

                }

                changeQuote()
                {

                }

                render()
                {
                this.content =
                    [
                       "asked MINDEF to let elite male athletes defer their NS.",
                       "asked God to let there be light",
                       "asked my parents to conceive me",
                       "asked John F. Kennedy to put a man on the moon",
                       "asked Lee Kuan Yew to create modern Singapore from a fishing village",
                       "asked Frodo to destroy the One Ring",
                       "asked for more cowbell",
                       "asked Rosa Parks to sit in the front of the bus",
                       "asked Martin Luther King what he dreamt of",
                       "asked Phelps to take it easy, so Schooling could win and I could take the credit",
                       "asked Indonesia to clean up Batam",
                       "asked Barack Obama to run for President",
                       "asked Obi-Wan Kenobi to save Amidala and her twins",
                       "asked Snape to take care of Harry",
                       "asked Def Leppard to pour some sugar on me",
                       "asked the residents of Yishun to vote for me, so I could ask MINDEF to let elite male athletes defer their NS"
                    ];

                    return (
                        <div>
                            <blockquote cite="http://www.teochewthunder.com">
                                I am glad I {this.content[0]}
                            </blockquote>

                            <p>
                            <button onClick={this.changeQuote}> More >> </button>
                            </p>
                        </div>
                    );               
                }
            }


Here, we call the super() method to initialize the constructor.
                constructor(props)
                {
                    super();
                }


Then we define state. It's an object with currentIndex as a property. The default value of this property is 0.
                constructor(props)
                {
                      super();
                      this.state = {"currentIndex": 0};
                }


Next, we bind the changeQuote() method to the constructor using the bind() method.
                constructor(props)
                {
                      super();
                      this.state = {"currentIndex": 0};
                      this.changeQuote = this.changeQuote.bind(this);
              }


In the changeQuote() method, we define a variable, tempIndex, which we set to 0. Then we run a random number function to get a number between 0 and the number of elements in the content array. Finally, we alter the currentIndex property of this component's state, setting it to the value of tempIndex.
                changeQuote()
                {
                    var tempIndex = 0;

                    tempIndex = Math.floor((Math.random() * (this.content.length)));

                    this.setState({"currentIndex": tempIndex});
                }


Now when we click the button, the quote changes! No page reloading needed.


You can skip the next part if you want to. It's just me being anal. Here, I use a Do-while loop to ensure that tempIndex doesn't randomize to the same value twice in a row. The chances of that happening aren't great, but like I said, I'm particular about some things.
                changeQuote()
                {
                    var tempIndex = 0;

                    do
                    {
                        tempIndex = Math.floor((Math.random() * (this.content.length)));
                    }
                    while (tempIndex == this.state.currentIndex);

                    this.setState({"currentIndex": tempIndex});
                }


All good? Cool, let's pretty this up a bit.

For the button, I'm going to set it to a nice light blue and place it center of the page. It'll have round corners and even a hover pseudoselector for some interactivity. Feel free to get creative!
        <style>
            button
            {
                display: block;
                padding: 0.5em;
                font-weight: bold;
                background-color: rgba(150, 150, 255, 0.5);
                color: #FFFFFF;
                border-radius: 5px;
                border: 0px;
                width: 100px;
                margin: 10% auto 0 auto;
                text-align: center;
            }

            button:hover
            {
                background-color: rgba(150, 150, 255, 1);
            }
        </style>


There's your pretty button, right there.


Next, we position the quoteContainer div. It will be 800 pixels in width. The margin property sets it to middle of the page with a 10% margin at the top, and I've given it a red outline for clarity.
        <style>       
            #quoteContainer
            {
                width: 800px;
                margin: 10% auto 0 auto;
                outline: 1px solid #FF0000;
            }

            button
            {
                display: block;
                padding: 0.5em;
                font-weight: bold;
                background-color: rgba(150, 150, 255, 0.5);
                color: #FFFFFF;
                border-radius: 5px;
                border: 0px;
                width: 100px;
                margin: 10% auto 0 auto;
                text-align: center;
            }

            button:hover
            {
                background-color: rgba(150, 150, 255, 1);
            }
        </style>


There it is.


Now let's style the blockquote tag! These tags may be rendered differently between browsers, so it behooves us to keep it as consistent as possible. I'm giving it a width 80% of quoteContainer's width, a height of 8 elements, and setting it in the middle of quoteContainer using the margin property. Text alignment and font is all up to you.
            #quoteContainer
            {
                width: 800px;
                margin: 10% auto 0 auto;
                outline: 1px solid #FF0000;
            }

            blockquote
            {
                display: block;
                width: 80%;
                height: 8em;
                margin: 0 auto 0 auto;
                text-align: center;
                font-family: georgia;
                font-size: 20px;
                font-weight: bold;
                line-height: 2em;
            }


Nice.


Now, the blockquote needs quotes! For this, we'll use the before and after pseudoselectors. Both these pseudoselectors will have display set to block and occupy the full width of the blockquote, with a decreased height and large font size.
            blockquote
            {
                display: block;
                width: 80%;
                height: 8em;
                margin: 0 auto 0 auto;
                text-align: center;
                font-family: georgia;
                font-size: 20px;
                font-weight: bold;
                line-height: 2em;
            }

            blockquote:before, blockquote:after
            {
                display: block;
                width: 100%;
                height: 0.5em;
                font-size: 3em;
            }


The difference, of course, is that before has an opening quote and is aligned left, while after is a closing quote and is aligned right.
            blockquote
            {
                display: block;
                width: 80%;
                height: 8em;
                margin: 0 auto 0 auto;
                text-align: center;
                font-family: georgia;
                font-size: 20px;
                font-weight: bold;
                line-height: 2em;
            }

            blockquote:before, blockquote:after
            {
                display: block;
                width: 100%;
                height: 0.5em;
                font-size: 3em;
            }

            blockquote:before
            {
                content: "\201C";
                text-align: left;
            }

            blockquote:after
            {
                content: "\201D";
                text-align: right;
            }


There are your quotes!


Let's prettify it even more with light blue borders.
            blockquote:before
            {
                content: "\201C";
                text-align: left;
                border-top: 3px double rgba(150, 150, 255, 0.5);
                border-right: 3px double rgba(150, 150, 255, 0.5);
            }

            blockquote:after
            {
                content: "\201D";
                text-align: right;
                border-bottom: 3px double rgba(150, 150, 255, 0.5);
                border-left: 3px double rgba(150, 150, 255, 0.5);
            }


You're doing good!


Now all that's left to do is remove the red outline.
            #quoteContainer
            {
                width: 800px;
                margin: 10% auto 0 auto;
                outline: 0px solid #FF0000;
            }


And there's our final product.

In closing...

Some friends, upon seeing this, have asked me to be careful because this could affect my career. These people are from the older generation of Singaporeans and typically paranoid about being fixed by the Government. They're still under the impression that the Government has the time or inclination to deal with petty issues. It's quite adorable.

Honestly, if this country's leaders can't take a joke, we have far bigger problems than me not having a job.

I'm glad I wrote this web tutorial to change your lives!
T___T

No comments:

Post a Comment