Showing posts with label web app. Show all posts
Showing posts with label web app. Show all posts

Tuesday, 27 August 2024

SGActive Plus: How much of a plus is it?

Booking sports facilities in Singapore is undergoing an upgrade... of sorts. I found this out earlier in the month when, on my daily evening swim at Clementi Swimming Complex, I was alerted to a poster by the entrance. This poster informed of an upcoming upgrade to the existing SGActive app on the 15th of August.

Informational posters.

On the 15th of August, the existing app would no longer be in use. All swim passes would need to be purchased on the new SGActive Plus web portal. Logging in to the new SGActive Plus web portal before the end of this month, would also get you 10 free credits. I wasted no time logging in because, being a regular user of the facilities, I needed to ensure that there would be minimal interruption to my lifestyle.

What happened

On the 13th of August, my current monthly pass expired and I had to purchase a new one. Due to the fact that it was not yet the 15th, I had to purchase my monthly swim pass from the old platform, with the understanding that this pass should still last an entire month. However, when I went to Clementi Swimming Complex again on the 15th of August, the gantries at the entrance had been blocked and now we needed new passes that had to be purchased from the new system.

No longer accessible.

The elderly attendant at the entrance cheerfully helped me navigate to the web portal and used one of my 10 free credits to purchase a pass for the day, assuring me that whatever I paid for my monthly pass previously, would be refunded in September. That was when I paused - if I had to do this every day till September arrived, the free credits would run out in no time. Surely this attendant should have helped me purchase a monthly pass (now known as a multi pass) instead? But no, he'd made an assumption. And now that I only had 9 credits left, attempting to purchase a 10 GSD monthly pass on the new platform would cost me a dollar. Not that I cared about the dollar - it was more the troublesome process of paying online. A process I could have avoided if this attendant had just thought to ask me instead of simply assuming a single pass was what I needed.

But that's OK. Didn't really want to get on this old man's case too much. It's not like he asked for any of this. I'm sure this was a great cushy retirement gig up to now.

Is this an improvement?

Well, in terms of the interface used to make purchases, I'm of the opinion that it would be extremely difficult to have any interface be worse than the one employed by the SGActive app. My feelings on this absolute abortion of an app have been made abundantly clear in my last review in 2018.

In terms of having to present an animated pass on my phone instead of simply having a reader at the gantry scan my Identity Card, I suppose one could argue that it's more secure. To use an Identity Card, one only needs to be in possession of said Identity Card. To use the pass displayed on the phone, one needs to be in possession of the phone and have the credentials (usually biometrics) necessary to log into the phone and access the SGActive Plus web account.

Animated
swim pass.


However, I'd also venture to say that in the event that a malicious actor came into possession of someone's Identity card, using it to obtain access to sports facilities wouldn't exactly be high on the list of things to do.

All said and done, I'm in favor of any proposed solution that will allow me to speak of the SGActive app in the past tense.

The real issue

So, the change was a good thing overall. The isssue I take with this, is the implementation of the change. I'm pretty sure I wouldn't have been the only one to be caught off-balance by the sudden nature of the cut-off.

One day we're using this platform, and a day later, we have to use something else. In most cases of system upgrades, old and new systems are run in parallel to minimize disruption to users while preparing them for the inevitable. Plus, with more time to prepare, the attendant would (hopefully) have done a better job. There'd be less confusion, for sure.

The new
10 SGD
monthly
swim pass.


The 10 free credits was a good idea, both as an incentive for early adoption, and a good way to ease users over to the new platform. Unfortunately, cases like mine happened; and I wonder if 12, maybe 15 free credits would have been better if we absolutely had to do that sudden cut-off. At the very least, we could have used those credits to figure the system out. Make some mistakes.

As it was, whatever money I spent on the old platform that couldn't be moved to the new, would be refunded next month. At the end of next month. It's fine, it's 10 bucks. Still annoying, but not a show stopper.

Final thoughts

Not to be too critical of change; it was about time. Though one might argue that the time for a new improved platform was roughly ten years ago when the app first came out; it was that bad.

Hope your day's going swimmingly well,
T___T

Tuesday, 22 December 2020

Web Tutorial: The Twelve Days of Christmas (Part 2/2)

I'm back!

What we're missing here are images. Let's not waste those beautiful SVG files! Import the SVGs from the svg folder.

src/components/Row/Row.js
import React from 'react';
import day1 from '../../svg/day1.svg';
import day2 from '../../svg/day2.svg';
import day3 from '../../svg/day3.svg';
import day4 from '../../svg/day4.svg';
import day5 from '../../svg/day5.svg';
import day6 from '../../svg/day6.svg';
import day7 from '../../svg/day7.svg';
import day8 from '../../svg/day8.svg';
import day9 from '../../svg/day9.svg';
import day10 from '../../svg/day10.svg';
import day11 from '../../svg/day11.svg';
import day12 from '../../svg/day12.svg';

import './Row.css';


Then modify the content array. Each object should now have a file property, corresponding to the day number... except for the element at index 0 because that's a dummy, remember?

src/components/Row/Row.js

let content = [
    { text: '', file: '' },
    { text: 'a partridge in a pear tree!', file: day1 },
    { text: 'two turtle doves, and', file: day2 },
    { text: 'three french hens', file: day3 },
    { text: 'four calling birds', file: day4 },
    { text: 'five gold rings', file: day5 },
    { text: 'six swans a-swimming', file: day6 },
    { text: 'seven geese a-laying', file: day7 },
    { text: 'eight maids a-milking', file: day8 },
    { text: 'nine ladies dancing', file: day9 },
    { text: 'ten lords a-leaping', file: day10 },
    { text: 'eleven pipers piping', file: day11 },
    { text: 'twelve drummers drumming', file: day12 }
];


We could now do this, and it would totally work. Just observe...

src/components/Row/Row.js
return (
    <div
        className={ 'Row' + (currentDay >= day ? '' : ' Hidden') }
    >
        <h1>{ content[day].text }</h1>
        <img src={ content[day].file } width="100" height="100" />
    </div>
);




But that's just not good enough. What we want is for n number of images for each day. So if it's Day Three, we want three french hens!

Declare an array, arr. Using a For loop and the push() method, make it have day elements. The elements can be empty; we just want to use it in a map() method.

src/components/Row/Row.js
let arr = [];
for (let i = 0; i < day; i++) arr.push('');


return (
    <div
        className={ 'Row' + (currentDay >= day ? '' : ' Hidden') }
    >


Then declare the component images. Use the map() method to add the required number of appropriate images!

src/components/Row/Row.js
let arr = [];
for (let i = 0; i < day; i++) arr.push('');

const images = arr.map((item, index) => (
        <img key={ day+'img'+index } src={ content[day].file } width="100" height="100" />
    )
);


return (
    <div
        className={ 'Row' + (currentDay >= day ? '' : ' Hidden') }
    >


Then do this.
src/components/Row/Row.js
return (
    <div
        className={ 'Row' + (currentDay >= day ? '' : ' Hidden') }
    >
        <h1>{ content[day].text }</h1>
        { images }
    </div>
);


Magic!


That's it!

Functionality-wise, we're good to go. But if you're interested in learning how to test the app, read on.

Testing

Testing for ReactJS apps isn't really Unit Testing, per se. It's more like DOM behavioral testing, if that makes sense. Let's work on the App component. We will use the ReactJS testing library. Basically, you write the tests and use the command npm test.

In this file, import React and the render and screen objects, and the userEvent object from the testing library. Also import App because that's what we're testing.

src/App/App.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import App from "./App";


We begin by describing App. This will contain the suite of App's tests.

src/App/App.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import App from "./App";

describe("App", () => {

});


First, we want to test that App responds to the up and down buttons. So we add a descriptor, and render App.

src/App/App.test.js
describe("App", () => {
    it("should react to up/down button", () => {
        render(
            <App/>
        );
    });

});


What we need next,is a way for the testing library to be able to identify the buttons. So in the App component, add the data-testid attribute for the buttons and give them values.

src/App/App.js
<div className="dayButtons">
    <div className="dayButton" data-testid="BtnUp" onClick={ BtnClickUp }>&#9650;</div>
    <div className="dayButton" data-testid="BtnDown" onClick={ BtnClickDown }>&#9660;</div>
</div>


Also add data-testid for the span tag that encapsulates currentDay.
src/App/App.js
<span data-testid="lblCurrentDay">
{ currentDay }
</span>


By default, day is 1. So if BtnUp is clicked, lblCurrentDay's content should be "2".
src/App/App.test.js
it("should react to up/down button", () => {
    render(
        <App/>
    );

    userEvent.click(screen.getByTestId("BtnUp"));
    expect(screen.getByTestId("lblCurrentDay").textContent).toBe("2");

});


And after that, if BtnDown is clicked, the value should revert to "1". That's how we test using userEvent.

src/App/App.test.js
it("should react to up/down button", () => {
    render(
        <App/>
    );

    userEvent.click(screen.getByTestId("BtnUp"));
    expect(screen.getByTestId("lblCurrentDay").textContent).toBe("2");
    userEvent.click(screen.getByTestId("BtnDown"));
    expect(screen.getByTestId("lblCurrentDay").textContent).toBe("1");

});


Next, we want to test that clicking BtnDown does nothing if the value is "1". So render App, and at the time of rendering, the value should be "1". If BtnDown is clicked, the value in lblCurrentDay should still be "1".

src/App/App.test.js
it("should react to up/down button", () => {
    render(
        <App/>
    );

    userEvent.click(screen.getByTestId("BtnUp"));
    expect(screen.getByTestId("lblCurrentDay").textContent).toBe("2");
    userEvent.click(screen.getByTestId("BtnDown"));
    expect(screen.getByTestId("lblCurrentDay").textContent).toBe("1");
});

it("should not react to down button if day is 1", () => {
    render(
        <App/>
    );

    userEvent.click(screen.getByTestId("BtnDown"));
    expect(screen.getByTestId("lblCurrentDay").textContent).toBe("1");
});


Naturally, what follows next is that we test to see if BtnUp does anything if the value is "12"! So render App, "click" BtnUp more than eleven times. The value should still be "12".

it("should not react to down button if day is 1", () => {
    render(
        <App/>
    );

    userEvent.click(screen.getByTestId("BtnDown"));
    expect(screen.getByTestId("lblCurrentDay").textContent).toBe("1");
});

it("should not react to up button if day is 12", () => {
    render(
        <App/>
    );

    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    userEvent.click(screen.getByTestId("BtnUp"));
    expect(screen.getByTestId("lblCurrentDay").textContent).toBe("12");
});


That was easy enough. Now we test the Row component. For this, we will not be testing button clicks, so there's no need to import the userEvent object.

src/components/Row/Row.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import Row from "./Row";


Copy the content array over from Row.js, but remove the file properties. We just want the text. And start describing Row.

src/components/Row/Row.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import Row from "./Row";

let content = [
    { text: '' },
    { text: 'a partridge in a pear tree!' },
    { text: 'two turtle doves, and' },
    { text: 'three french hens' },
    { text: 'four calling birds' },
    { text: 'five gold rings' },
    { text: 'six swans a-swimming'},
    { text: 'seven geese a-laying' },
    { text: 'eight maids a-milking' },
    { text: 'nine ladies dancing' },
    { text: 'ten lords a-leaping' },
    { text: 'eleven pipers piping' },
    { text: 'twelve drummers drumming' }
];


describe("Row", () => {

});


Next, we render Row. But remember that Row expects two attributes to be passed in - day and currentDay. Let's have day's value to be 2. currentDay can be any value because this test is for day and the value of currentDay doesn't matter.

src/components/Row/Row.test.js
describe("Row", () => {
    it("should display title according to day", () => {
        let day = 2;
        let currentDay = 1;

        render(
            <Row
                day={ day }
                currentDay={ currentDay }
            />
        );

    });
});


Here, we expect the text from the content array, pointed to by day, to be part of the DOM. Because that's what happens - the text is displayed according to the value of day.

src/components/Row/Row.test.js
describe("Row", () => {
    it("should display title according to day", () => {
        let day = 2;
        let currentDay = 1; // any value

        render(
            <Row
                day={ day }
                currentDay={ currentDay }
            />
        );

        expect(screen.queryByText(content[day].text)).toBeInTheDocument();
    });
});


The next test again tests the value of day. The number of SVGs displayed should equal the value of day. So we use the queryAllByTestId() method of the screen object, pass in "imgDay" as an argument, and test for the number of elements returned!

src/components/Row/Row.test.js
describe("Row", () => {
    it("should display title according to day", () => {
        let day = 2;
        let currentDay = 1; // any value

        render(
            <Row
                day={ day }
                currentDay={ currentDay }
            />
        );

        expect(screen.queryByText(content[day].text)).toBeInTheDocument();
    });

    it("should display number of images according to day", () => {
        let day = 2;
        let currentDay = 1; // any value

        render(
            <Row
                day={ day }
                currentDay={ currentDay }
            />
        );

        expect(screen.queryAllByTestId("imgDay")).toHaveLength(2);
    });

});


Don't forget to add the data-testid attribute!

src/components/Row/Row.js
<img data-testid="imgDay" key={ day+'img'+index } src={ content[day].file } width="100" height="100" />


Now the value of currentDay matters, because we're testing currentDay against day. Again, render Row with the same attributes.
src/components/Row/Row.test.js
describe("Row", () => {
    it("should display title according to day", () => {
        let day = 2;
        let currentDay = 1; // any value

        render(
            <Row
                day={ day }
                currentDay={ currentDay }
            />
        );

        expect(screen.queryByText(content[day].text)).toBeInTheDocument();
    });

    it("should display number of images according to day", () => {
        let day = 2;
        let currentDay = 1; // any value

        render(
            <Row
                day={ day }
                currentDay={ currentDay }
            />
        );

        expect(screen.queryAllByTestId("imgDay")).toHaveLength(2);
    });

    it("should be hidden if current day is lower than day", () => {
        let day = 2;
        let currentDay = 1;

        const { container } = render(
            <Row
                day={ day }
                currentDay={ currentDay }
            />
        );
    });

});


If currentDay is less than day, the class of Row should have Hidden.

src/components/Row/Row.test.js
it("should be hidden if current day is lower than day", () => {
    let day = 2;
    let currentDay = 1;

    const { container } = render(
        <Row
            day={ day }
            currentDay={ currentDay }
        />
    );

    expect(container.firstChild.classList.contains("Hidden")).toBe(true);
});



And finally, the reverse of that last test.

src/components/Row/Row.test.js

it("should be hidden if current day is lower than day", () => {
    let day = 2;
    let currentDay = 1;

    const { container } = render(
        <Row
            day={ day }
            currentDay={ currentDay }
        />
    );

    expect(container.firstChild.classList.contains("Hidden")).toBe(true);
});

it("should not be hidden if current day is not lower than day", () => {
    let day = 2;
    let currentDay = 3;

    const { container } = render(
        <Row
            day={ day }
            currentDay={ currentDay }
        />
    );

    expect(container.firstChild.classList.contains("Hidden")).toBe(false);
});


So when you run the test suite, you should see this.


That's it for testing. These are really simple tests, but then again this is a really simple app. There's always more you can do, but I think this range of tests is enough for now.

True love is so Row-mantic,
T___T

Sunday, 20 December 2020

Web Tutorial: The Twelve Days of Christmas (Part 1/2)

Christmas is coming!

And for the annual Christmas-themed web tutorial, we're going to embark on something deliciously frivolous - a ReactJS App that interactively shows the lyrics of the Christmas carol The Twelve Days of Christmas.

For this, I will be assuming you already know the steps to creating an app through NodeJS Package Manager. (And if you don't here's a link!) And as such, I'll mainly be walking through the setup, the implementation and testing. This isn't really meant to be a mobile app, so I'm not going to bother with the bootstrapping and shit.

Basic setup

We start off with a freshly created ReactJS App. There are some changes I like to make to a newly-minted app, such as changing the title. You don't have to, and choosing not to do so will not affect your app in any way.

public\index.html
<title>12 Days of Christmas</title>


Now let's do something a bit more useful. In the src folder, there's where we will be working most of the time. I am going to be working with a lot of SVG files, twelve of them, to be exact. So let's create a sub-folder, svg, within src.

In that folder are the SVG fies I will be working with. All of them were generated using Method Draw SVG Editor, a free tool which served its purpose superbly.

If you want the files, they are at this link.

Got all that? This is the easy part, so don't get lost here.

Clean up App

In the src folder, we will need App.js and App.css; however, we will not need whatever NPM generated for us, so feel free to remove all that code inside those two files. We can keep index.js and index.css; no need to touch those.

This isn't going to be mobile-friendly, so for the App CSS class, just align all text center.

src/App.css
.App {
    text-align: center;
}


Here, we'll need to import the CSS file. Also, import the React from the react library.

src/App.js
import React from 'react';
import './App.css';


Add the App() function.

src/App.js
import React from 'react';
import './App.css';

function App() {

}


It should return JSX - a div with a class name of App (which we've styled) and some text in a h1 tag. Note the extra space in the middle of that text; we're going to fill it right up later!

src/App.js
import React from 'react';
import './App.css';

function App() {
    return (
        <div className="App">
            <h1>On the  day of Christmas my true love gave to me</h1>
          
        </div>
    );

}


Finally, we export App so that the parent (index.js) can use it.

src/App.js
import React from 'react';
import './App.css';

function App() {
    return (
        <div className="App">
            <h1>On the  day of Christmas my true love gave to me</h1>
          
        </div>
    );
}

export default App;


Take a look!

The aim here is to be able to adjust the number of the day. To do that, we'll need controls. Add this placeholder to the text.

src/App.js
<h1>On the { dayControls } day of Christmas my true love gave to me</h1>


Now, within the App() function, we will create the dayControls component. It will consist of a div with a class of dayControl.

src/App.js
import React from 'react';
import './App.css';

function App() {
    const dayControls = (
        <div className="dayControl">

        </div>
    );


    return (
        <div className="App">
            <h1>On the { dayControls } day of Christmas my true love gave to me</h1>
          
        </div>
    );
}

export default App;


Within that div, we have two more divs, styled using dayText and dayButtons, respectively.

src/App.js
import React from 'react';
import './App.css';

function App() {
    const dayControls = (
        <div className="dayControl">
            <div className="dayText">

            </div>

            <div className="dayButtons">

            </div>

        </div>
    );

    return (
        <div className="App">
            <h1>On the { dayControls } day of Christmas my true love gave to me</h1>
          
        </div>
    );
}

export default App;


And in that second div, we'll put two more divs. Those will be our up and down buttons. Style them using the class dayButton.

src/App.js
import React from 'react';
import './App.css';

function App() {
    const dayControls = (
        <div className="dayControl">
            <div className="dayText">

            </div>

            <div className="dayButtons">
                <div className="dayButton">&#9650;</div>
                <div className="dayButton">&#9660;</div>

            </div>
        </div>
    );

    return (
        <div className="App">
            <h1>On the { dayControls } day of Christmas my true love gave to me</h1>
          
        </div>
    );
}

export default App;


OK, just some styling here. dayControl needs to be inline, but we also want to specify width, so the display property will be set to inline-block.

src/App.css
.App {
    text-align: center;
}

.dayControl {
    display: inline-block;
    width: 4em;
}


The dayButtons div will float left of its parent. We'll give it a nominal width and height.

src/App.css
.App {
    text-align: center;
}

.dayControl {
    display: inline-block;
    width: 4em;
}

.dayButtons {
    float: left;
    width: 1em;
    height: 100%;
}


From here on, the dayButton CSS class is mostly a matter of aesthetic choice.

src/App.css
.App {
    text-align: center;
}

.dayControl {
    display: inline-block;
    width: 4em;
}

.dayButtons {
    float: left;
    width: 1em;
    height: 100%;
}

.dayButtons .dayButton{
    width: 100%;
    height: 50%;
    text-align: right;
    background: transparent;
    color: rgb(0, 0, 0);
    font-weight: normal;
    font-size: 0.5em;
    cursor: pointer;
}

.dayButtons .dayButton:hover{
    color: rgb(100, 100, 100);    
    font-weight: bold;
}


And this is what it all looks like.

Now let's introduce another variable in there - currentDay. This is the variable that is eventually going to control how it all looks.

src/App.js
const dayControls = (
    <div className="dayControl">
        <div className="dayText">
            { currentDay }
        </div>

        <div className="dayButtons">
            <div className="dayButton">&#9650;</div>
            <div className="dayButton">&#9660;</div>
        </div>
    </div>
);


currentDay is state data, and to manage that, we'll use React hooks. To do that, we need to import useState.

src/App.js
import React, { useState } from 'react';


Then, using the useState() function we just imported, we declare currentDay as a hook, and setCurrentDay as the function that alters the value of that piece of data. The default will be 1, which we'll pass into useState as an argument.

src/App.js
import React, { useState } from 'react';
import './App.css';

function App() {
    const [currentDay, setCurrentDay] = useState(1);

    const dayControls = (
        <div className="dayControl">
            <div className="dayText">
                { currentDay }
            </div>

            <div className="dayButtons">
                <div className="dayButton">&#9650;</div>
                <div className="dayButton">&#9660;</div>
            </div>
        </div>
    );


In the CSS, let's set the text to red.

src/App.css
.dayControl {
    display: inline-block;
    width: 4em;
}

.dayText {
    color: #FF0000;
    float: right;
    width: 3em;
}


.dayButtons {
    float: left;
    width: 1em;
    height: 100%;
}


Yep!
Let's do some magic here. Add onClick events to the buttons, and declare their respective functions.

src/App.js
const [currentDay, setCurrentDay] = useState(1);

const BtnClickUp = () => {

}

const BtnClickDown = () => {
      
}


const dayControls = (
    <div className="dayControl">
        <div className="dayText">
            { currentDay }
        </div>

        <div className="dayButtons">
            <div className="dayButton" onClick={ BtnClickUp }>&#9650;</div>
            <div className="dayButton" onClick={ BtnClickDown }>&#9660;</div>
        </div>
    </div>
);


When the up button is clicked, we want to use setCurrentDay() to increment the value of currentDay. But only if currentDay is not still less than 12. Similarly, we'll set a floor for currentDay at 1, if the down button is clicked. Because we're only going to go from Day 1 to 12, yes?

src/App.js
const BtnClickUp = () => {
    if (currentDay < 12) {
        setCurrentDay(currentDay + 1);        
    }

}

const BtnClickDown = () => {
    if (currentDay > 1) {
        setCurrentDay(currentDay - 1);           
    }  
      
}


This guy should now respond to your button clicks!

But that's not nearly enough. Let's add a suffix to that number. In this component, right after currentDay, add a sup tag. And in that tag, introduce the variable daySuffix, which is an array. The index of the array will be the value of currentDay, minus 1.

src/App.js
const dayControls = (
    <div className="dayControl">
        <div className="dayText">
            { currentDay }
            <sup>
                { daySuffix[currentDay - 1] }
            </sup>

        </div>

        <div className="dayButtons">
            <div className="dayButton" onClick={ BtnClickUp }>&#9650;</div>
            <div className="dayButton" onClick={ BtnClickDown }>&#9660;</div>
        </div>
    </div>
);


And now, we will add the daySuffix array. It's basically a list of suffixes that come after each number - 1st, 2nd, 3rd and so on until we reach 12. There's probably a less heavy-handed way of doing this, but it's just twelve elements and there are better uses for my time.

src/App.js
    const [currentDay, setCurrentDay] = useState(1);

    const daySuffix = [
        "st",
        "nd",
        "rd",
        "th",
        "th",
        "th",
        "th",
        "th",
        "th",
        "th",
        "th",
        "th"
    ]


    const BtnClickUp = () => {
        if (currentDay < 12) {
            setCurrentDay(currentDay + 1);        
        }
    }


Now try it!

That's just the first line of the song, though. The rest will be handled by the use of a reusable component. I'm gonna call it, rather unimaginatively, Row. First, we do an import. We will import Row from the directory components and subdirectory Row, both of which we haven't created yet.

src/App.js
import React, { useState } from 'react';
import './App.css';
import Row from './components/Row';


And now we're going to do something really lazy. In the return statement, we will add twelve instances of the Row component. In each one, we will pass in the value of currentDay as an attribute. We will also pass in the day attribute, numbered from 12 to 1.

src/App.js
return (
    <div className="App">
        <h1>On the { dayControls } day of Christmas my true love gave to me</h1>
        <Row currentDay = {currentDay} day="12" />
        <Row currentDay = {currentDay} day="11" />
        <Row currentDay = {currentDay} day="10" />
        <Row currentDay = {currentDay} day="9" />
        <Row currentDay = {currentDay} day="8" />
        <Row currentDay = {currentDay} day="7" />
        <Row currentDay = {currentDay} day="6" />
        <Row currentDay = {currentDay} day="5" />
        <Row currentDay = {currentDay} day="4" />
        <Row currentDay = {currentDay} day="3" />
        <Row currentDay = {currentDay} day="2" />
        <Row currentDay = {currentDay} day="1" /> 
           
    </div>
);


Next, create the directory components. In that directory, create another directory Row. Then create Row.js within it.

In that file, we will import React again.

src/components/Row/Row.js
import React from 'react';


Also import Row.css. We will create that file soon.

src/components/Row/Row.js
import React from 'react';
import './Row.css';


And from here, we define the function Row(), with a return statement. And then export Row.

src/components/Row/Row.js
import React from 'react';
import './Row.css';

function Row(props) {       
    return (

    );
}


export default Row;


Create index.js, and add this line. Now whenever someone imports from the Row directory, the first file to reference will be index.js. And index.js will export default (which is Row) from the Row directory. This has the advantage of the caller not needing to know the implementation details of Row (what component name to use, etc), but just needing to know that the component is from the Row directory.

src/components/Row/index.js
export { default } from './Row';


Now back to the Row component. Remember we passed in two attributes? They can be accessed from props. So declare day and currentDay accordingly.

src/components/Row/Row.js
function Row(props) {
    let day = props.day;
    let currentDay = props.currentDay;


    return (

    );
}


Then declare content as an array. Its elements are objects, each with a single property for now. The property is text, and it is the rest of the lyrics, according to day. The element at index position 0 (the first one) is just a dummy.

src/components/Row/Row.js
function Row(props) {
    let day = props.day;
    let currentDay = props.currentDay;

    let content = [
        { text: '', file: '' },
        { text: 'a partridge in a pear tree!' },
        { text: 'two turtle doves, and' },
        { text: 'three french hens' },
        { text: 'four calling birds' },
        { text: 'five gold rings' },
        { text: 'six swans a-swimming'},
        { text: 'seven geese a-laying' },
        { text: 'eight maids a-milking' },
        { text: 'nine ladies dancing' },
        { text: 'ten lords a-leaping' },
        { text: 'eleven pipers piping' },
        { text: 'twelve drummers drumming' }
    ];


    return (

    );
}


In the return statement, we return a div.
src/components/Row/Row.js
return (
    <div>

    </div>

);


Here, we pass in the text property of the relevant element in the content array, pointed to by day.
src/components/Row/Row.js
return (
    <div>
        <h1>{ content[day].text }</h1>
    </div>
);


So, here are our rows, but it's not really what we want, is it?

Create the CSS file. Row should take up 100% width and have, say, 200 pixels in height. In addition, set the overflow property to hidden. I added the transition property just for fun. The Hidden class is at zero transparency and height.

src/components/Row/Row.css
.Row {
    width: 100%;
    height: 200px;
    overflow: hidden;
    transition: all 1s;
}

.Hidden {
    opacity: 0;
    height: 0px;
}


Now in here, set the class to be Row, and if currentDay is less than day, add Hidden. That means if whatever day is currently shown, only the Row components with the day attribute lesser than or equal to currentDay, will be shown. In plain English, if you are at Day Four, you only want to see rows One to Four.

src/components/Row/Row.js
return (
    <div
        className={ 'Row' + (currentDay >= day ? '' : ' Hidden') }
    >
        <h1>{ content[day].text }</h1>
    </div>
);


Does it work? Try clicking the up and down buttons. The appropriate rows should appear and disappear. There's a whole lot of white space because we set the height to 200 pixels, but that's deliberate. We're making space for the images.

Next

We'll put in the images, and go through some rudimentary automated tests!

Sunday, 19 April 2020

Why some things should not be automated

We live in a world that is increasingly being automated. Mundane tasks such as ordering food and buying groceries is now done via means of Food Delivery apps. Trips to the bank have long been supplanted by online banking services. No longer do you need to stand in line to purchase a movie ticket - mobile apps make this a huge convenience.

In the software industry, where software developers are responsible for much of this automation, it comes as no surprise that many work-related tasks are now subject to this very same automation. Automated DevOps is a thing. Manual testing still has its place, but test-driven development via automated testing is the de facto standard. Linters now beautify and neaten huge chunks of code at a click of a mouse button, a task that used to be manually carried out by the more obsessive of us.

Yet I say, some things in life should not be automated. Yes, they can be automated and indeed many people are already doing so, but my contention is that they shouldn't. Because the very act of manually doing them is what gives those acts meaning, and builds character.

Developing good habits

To give this some context, I'd like to relate an anecdote from last year.

My first-ever wedding anniversary was coming up, and I was stressing out over what would qualify as an acceptable anniversary gift. Like the average unimaginative guy, I was leaning towards jewelry, but my taste in aesthetics has always been horrible. And then my wife called me while I was at work one day and told me she had decided to buy another phone for some reason or other. She'd narrowed it down to two models, but couldn't decide and wanted an opinion. The first model cost 700 SGD and had a lot of features. The second model cost half that, had all the features she needed, but she was afraid it would be obsolete in a year and didn't want to risk wasting her money.

700 SGD? Shit, I could handle that.

I told her to head for the top drawer of the cupboard in our bedroom, look in the bottom left corner of that drawer. In it was a white envelope containing exactly 720 SGD. I told her to use that to buy her phone. I also informed her, quite gleefully, that this was her anniversary gift.

In one move, I had achieved a few objectives. I had gotten my wife something she actually wanted for an anniversary gift, and the beauty of it was, I hadn't needed to choose it, or even buy it myself. And my wife now thinks I'm some kind of miracle worker who can produce extra cash out of nowhere.

But that's enough bragging about how clever I am. The real question is - how did I happen to have that envelope of cash lying around just for this occasion?

Squirreling money away

You see, I have this habit of squirreling money away. 2 SGD a day amounts to 730 SGD a year. Over time, I'd simplified the formula to 10 SGD every five days, or just 60 SGD a month. The 720 SGD in the envelope was cash I'd squirreled away for the year of 2017, and forgotten about until that point!

It's not like I even needed the money. But years of being financially strapped and slaving away for cheap-bastard sweatshop bosses had ingrained this little habit in me. Even now, drawing more than twice what I used to earn as a web developer, I've refused to let go of this tendency to put cash aside.

Every month, my tenants pay me more than that 720 SGD in cash. I could quite easily put one month's rental in the envelope and forget about saving petty cash for the rest of the year. It would be convenient, and quite doable. And perhaps utterly disastrous in the long run. The only reason why I find it so easy to save money when other people have all sorts of problems, is because I've made a habit of it. It's something I do without thinking because I do it regularly and constantly. Were I to ease up just for the sake of making it even more convenient, sooner or later, I would lose that habit simply because I allowed it to atrophy.

Drawing a bigger paycheck can properly screw up your ability to live with less money. Or, if you retain the habits you developed when you were a lot poorer, make you a heck of a lot more resilient.

So yeah... I didn't automate that task. Or perhaps I did, but in a very manual way. Does that make sense?

Giving acts meaning

Since I began life as a working professional roughly two decades ago, I've been paying the Womb Rental. And by that, I mean that amount of money I give my mother every month. Some like to call it Parental Protection Money or Uterus Tax, but to each his own.

It wasn't always this way. When I was younger, I found this a drag and kept thinking of ways to smoothen the process. Maybe give her access to my bank account so she could draw the money herself? Maybe set up some kind of automated monthly payment to her bank account from mine? Or give her a whole lot of money at one shot so I could go for months without going through all that hassle?

Mom vetoed all of that shit. She wanted me to personally hand her cash, every month. It didn't matter how much, as long as I could afford it.

Funds transfer

At that time, I didn't understand. Wasn't my way more convenient for everyone involved? Why the insistence on the manual method? But after so many years of doing this, it's starting to make sense. As outdated as I think my mother's worldview is, and as desperate as I am not to turn into my parents, I'm beginning to think Mom may have had a point there.

You see, it wasn't about the money. It was never just about the money.

What my mother wanted was for me to get used to having to consider her needs at least once monthly. To get used to contributing to her finances ever so often. To prepare me for the day I would have to truly step up. My software developer mindset would have gotten in the way of all that.

And it's worked. Well, kind of. I take it as a challenge to remember to pay the Womb Rental on time. Every time my mother has to call me to remind me because I missed the date, I consider it a personal failure, and resolve to do better.

To Conclude...

Efficiency may be one of the ultimate goals of programming, but it isn't everything. It makes life easier immediately, but sometimes we need to look at long-term effects.

I really should automate this signoff,
T___T

Sunday, 24 June 2018

The Rope Anatomy Analogy

My time in the Republic of Singapore Navy brought me into contact with rope. Lots of rope. Miles and miles of rope. So today, I wanna talk about... yep, you guessed it. Rope.

A rope looks like just some long thick string you use to tie stuff. But it's actually a work of physics. Let's take a look at how a typical three-strand rope is constructed.

Firstly, we have fibers. They could be hemp, manila or even hair. Each bunch of fibers is twisted in a clockwise direction to form a rope yarn.

Now, a group of rope yarns are twisted together in an anti-clockwise direction to form a strand. Got that so far?

When you have three strands, put them together and twist them in a clockwise direction. This forms your basic rope.

Taken from
http://www.boatsafe.com/marlinespike/rope.htm


What's with all that clockwise and anti-clockwise stuff, you may ask. Well, when you twist something in any direction, its natural instinct is to twist back into its original shape. This means that if a bunch of fibers was twisted in a clockwise direction to form a rope yarn and made to stay that way, there would be potential energy locked within that rope yarn just waiting to un-twist back the way it was. If a bunch of these yarns were then twisted in an anti-clockwise direction into a strand, the different directional forces would counteract each other. Now you add one more layer of opposing force on top of this - twisting the strands clockwise into a rope - and this is where the strength of a rope comes from.

The Web Development Analogy

A web application goes through layering as well. Different components such as session management, security protocols, database connections and so on, make up the core of the application. On top of those, another layer is built. This is the DOM, and the user interface. These layers interact with each other, turning the web application into a strong sturdy piece of engineering - just like the humble rope.

Nope, a web application isn't simply just a web application. Even if you didn't have to deal with all the infrastructure and scaffolding; even if you simply coded features, that doesn't mean those layers aren't there. That is precisely why it's often not enough for a developer to know how to code either in the back-end or in the front-end. Developers have to understand how the different layers interact with and complement each other; how every layer affects the layer preceding it and following it. Because, similar to how a rope gets its strength from the opposing tensions generated by its different layers, that is how a web application gets its strength.

Where there's life, there's rope!
T___T

Thursday, 28 January 2016

How your Mobile Apps are made

Mobile applications (or "apps"), technically, come in three different forms - native, web and hybrid. To most people, they are "mobile apps", and that's all there is to it. Laypeople have that luxury. But the web developer needs to understand that there are differences - and not just in terms of Android, iOS or Windows Phone.

Native 

Native apps are created in a language native to their platform. Native apps for iOS are written in Objective-C or Swift, and native apps for Android are written in Java. So if your app needs to function on both platforms, you will need to write that same application twice - once for each platform. As you may conclude from this, development of a native app tends to take a bit of time.

Of course, since the app is created in a native language and can interface with the device's operating system and hardware directly, it has a distinct advantage in terms of speed and versatility. Anything that your device can do, your app can manipulate. Need to access the camera on your tablet? Maybe tune the brightness and sound? No problem. It's all there.

Here's an example of Facebook's Android app.

Accessing the
native app via
an icon.


Facebook for
Android.

Web 

Web apps, in the mobile context, are like your desktop/laptop browser-based websites. You type in a URL or access a bookmark, and with a working internet connection, you're able to access the functionality of those sites. These are written in HTML5, styled with CSS and coded with JavaScript. Again, like any other website. The only difference here is that these sites are designed to fit onto the screen of your mobile device. Functionality is limited to whatever traditional web applications can do - retrieval/updating/deletion of data, animations, playing of videos, and so on.

You don't need any special skills outside of the average web developer's repertoire (I'm making some huge assumptions here, but bear with me) to create a web application. But you're severely limited by the need for a browser.

Here's an example of accessing the Facebook and Quora's mobile web apps.

First, you access
the internet via a
web browser's icon,
like Chrome.

Next, you type in the
URL of Facebook...

... or Quora.

Note that we first start up Chrome (or any web browser), then type in the URL.

Hybrid 

Hybrid apps are, simplistically put, web apps that have been ported over to a mobile device in a native app-like wrapper (via platforms such as Cordova and Phonegap) and therefore simulate the behavior of native apps. How so? Well, for starters, you no longer need to start up your browser and access a URL. You merely need to click on your app's icon, just like a native app. Limited access to the native functions of your mobile device can be accomplished via plug-ins. As you may surmise, this slows things down somewhat on the hardware side of things.

Development of a hybrid app tends to be fast, though. You're basically taking the same web app and porting them to different platforms. Write once, run anywhere. Maintenance is also markedly easier. Excellent if you just want a quick-and-dirty prototype.

Here's an example of Quora's hybrid app.

Editor's Note: It's not confirmed that Quora is a hybrid app. But the interfaces of their web app and mobile app look suspiciously similar.

As in a native app,
you access the app
directly via an icon.

Quora

 

What to use for Mobile Development? What's better? 

This probably feels like a cop-out answer but, as with most things, it really depends on what you're going for. Is this a final product or a prototype? How crucial is speed or development time? Do you foresee a lot of maintenance? Your answer will determine the direction you eventually head towards.

Thanks for reading. Much app-reciated.
T___T