Saturday, 18 June 2022

Web Tutorial: The Japanese Language Trainer App (Part 1/4)

One of the struggles I have encountered in the past year was with learning a new language - specifically, Japanese. I was trying to learn to read and write Japanese characters when something occurred to me: I could totally create an app to help train me to read and write in Japanese. And I would even do it in ReactJS!

Thus, today we will be walking through the steps of how to create a ReactJS app to randomly display Japanese characters, and for the user to click on the correct pronounciation.

What you will need for this...

As with any app that requires you to set up an app in NodeJS, please g through the instructions here. You should then have an app created, with all the directories and such. I am just using the name "japtrainer" for this app.

We will also use this image below, just as decoration. This should be saved in the img directory of the src directory. That's my recommendation, but you can save that to a different directory as long as you reference it correctly afterwards.

src/img/bg.jpg



Change the title of the app.

public/index.html
<title>J-Trainer</title>


Now we need to start right from this level, in the src directory. Let's clear the App.css file and add a specification for App. The width is 600 pixels, and the margin property sets the div in the middle.

src/App.css
.App {
    width: 600px;
    margin: 0 auto 0 auto;
}


Inside App, we have three divs - styled using CSS classes Top, Middle and Bottom. All of them take up 100% of the parent, but have different heights.

src/App.css
.App {
    width: 600px;
    margin: 0 auto 0 auto;
}

.App .Top {
    width: 100%;
    height: 150px;
}

.App .Middle {
    width: 100%;
    height: 350px;
}

.App .Bottom {
    width: 100%;
    height: 200px;
}


Preemptively, we create the Hide CSS class for cases where stuff needs to be hidden. For this, the visibility property is set to hidden and the cursor property is set to default.

src/App.css
.App .Bottom {
    width: 100%;
    height: 200px;
}

.Hide {
    visibility: hidden;
    cursor: default;
}


Again, pre-emptively, because I'd rather not revisit this file later, we set styling for buttons. I'm going for the round-cornered, orange button, but this is strictly aesthetic and you need to find your own preferences here. If the button is disabled, transparency for background color is set to 50%. There is also the btnSmall class, where the font size is set to be 50% of the default. You will see all this in action soon.

src/App.css
.Hide {
    visibility: hidden;
    cursor: default;
}

button {
    width: 10em;
    height: 2em;
    font-weight: bold;
    color: #FFFFFF;
    background-color: #FF4400;
    text-align: center;
    border: 0px solid #FFFFFF;
    border-radius: 5px;
}

button[disabled] {
    background-color: rgba(255, 200, 0, 0.5);
}

.btnSmall {
    font-size: 0.5em;  
}


Next, clear the file App.js. We are going to rebuild this entirely. Begin by importing React and useState - the latter will be useful when we want to set state. Also, import the CSS file.

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


We follow up by defining the App() function here, and then exporting it as the default.

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

function App() {

}

export default App;


In App(), we return this. This is the main layout for the app, and you will see that the styling for Top, Middle and Bottom have already been defined earlier.

src/App.js
function App() {
  return (
    <div className="App">
      <div className="Top">

      </div>
      <div className="Middle">

      </div>  
      <div className="Bottom">

      </div>       
    </div>
  );
}


Now we are going to set the state for some variables, along with their respective setters.

charset - this is the character set we will be using. It's an array and the default value is null.
started - this is a Boolean value we use to determine if the exercise has started, with default value being false.
remaining - this is an integer, default value 20, which defines how many questions there are in an exercise.
answer - this is a string that is used to reference charset. Default is an empty string.
question - this will be an object taken from the array charset. Default is null.
result - this is an integer that keeps track of how many questions the user got right. By default, it is 0.
usedQuestions - this is an array where question will be stored, so as to ensure there are no repeats. It's an empty array by default.
maxRemaining - this is a constant, and we set it to 20. It will not be changed within this app.

src/App.js
function App() {
  const [charset, setCharset] = useState(null);
  const [started, setStarted] = useState(false);
  const [remaining, setRemaining] = useState(20);
  const [answer, setAnswer] = useState("");
  const [question, setQuestion] = useState(null);
  const [result, setResult] = useState(0);
  const [usedQuestions, setUsedQuestions] = useState([]);
  const maxRemaining = 20;


  return (
    <div className="App">
      <div className="Top">

      </div>
      <div className="Middle">

      </div>  
      <div className="Bottom">

      </div>       
    </div>
  );
}


The Header component

We are going to begin adding components, starting with Header. First do an import - we will follow up later from that directory.

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

function App() {


Add the Header component into the div styled using Top. The state values we defined earlier (along with their setters) are passed into the component as properties.

src/App.js
return (
<div className="App">
  <div className="Top">
    <Header
      charset={ charset }
      started={ started }
      remaining={ remaining }
      usedQuestions={ usedQuestions }
      maxRemaining={ maxRemaining }
      setStarted={ setStarted }
      setCharset={ setCharset }
      setRemaining={ setRemaining }
      setAnswer={ setAnswer }
      setResult={ setResult }
      setQuestion={ setQuestion }
      setUsedQuestions={ setUsedQuestions }
    />

  </div>
  <div className="Middle">

  </div>  
  <div className="Bottom">

  </div>       
</div>
);


You won't see anything yet. We need to create the Header component. For this, we should already have a components directory in the src directory. In it, we will create another directory, named Header. In there, we should have Header.js, Header.css and index.js. Create all these files.

Do this in index.js so that any export from the Header directory will not need to know what filename to call, since the default is index.js. It's just a good practice.

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


Import the same things we did for App.js. Import the CSS as well.

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


Create the Header() function, making sure that props is a parameter, and put the export statement at the end to correspond with the export statement in index.js.

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

function Header(props) {
 
}

export default Header;


We begin by initializing all the variables from props.

src/components/Header/Header.js
function Header(props) {
    let charset = props.charset;
    let setCharset = props.setCharset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let usedQuestions = props.usedQuestions;
    let setStarted = props.setStarted;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setResult = props.setResult;
    let setUsedQuestions = props.setUsedQuestions;
    let setQuestion = props.setQuestion;  

}


Depending on the value of started, we return different things.

src/components/Header/Header.js
function Header(props) {
    let charset = props.charset;
    let setCharset = props.setCharset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let usedQuestions = props.usedQuestions;
    let setStarted = props.setStarted;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setResult = props.setResult;
    let setUsedQuestions = props.setUsedQuestions;
    let setQuestion = props.setQuestion;
    
    if (started) {
        return (
        
        );
    } else {
        return (

        );          
    } 
 
}


If started is true, we have two divs one with the CSS style Title and the other with Controls.

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">

            </div>
        </>  
         
    );
} else {
    return (

    );          
}   


In here,  we have a bit of HTML, most notably, a button tag. The text depends on the value of remaining. If remaining is 0, we change the text to "Restart" and if not, that means that there are still questions remaining, so the most obvious option is to "Cancel".

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <br />
                    <button>
                        { remaining === 0 ? "RESTART ➤ " : "CANCEL ☓" }
                    </button>
                </div>

            </div>
        </>            
    );
} else {
    return (

    );          
}   


Now, if the exercise has not started, we still have the same divs with the CSS classes of Title and Controls.

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <br />
                    <button>
                        { remaining === 0 ? "RESTART ➤ " : "CANCEL ☓" }
                    </button>
                </div>
            </div>
        </>            
    );
} else {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">

            </div>
        </>

    );          
}   


For this, we have two radio buttons, labelled "Hiragana" and "Katakana" respectively. And we have a button with the "BEGIN" text. The disabled attribute is set depending on whether the value of charset is null.

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <br />
                    <button>
                        { remaining === 0 ? "RESTART ➤ " : "CANCEL ☓" }
                    </button>
                </div>
            </div>
        </>            
    );
} else {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <input type="radio" name="charset"  /> Hiragana <br />
                    <input type="radio" name="charset" /> Katakana <br />
                    <br />
                    <button disabled={ charset === null ? true : false }>
                        BEGIN ➤
                    </button>
                </div>

            </div>
        </>
    );          
}   


Now for the CSS. Title takes up full width and 80 pixels height. We float it left and center the text. We also give it a solid grey border and use the image for its background. I've tweaked the letter-spacing property for prettiness.

src/components/Header/Header.css
.Title {
    width: 100%;
    height: 80px;
    float: left;
    text-align: center;
    border-bottom: 1px solid #DDDDDD;
    background: url(../../img/bg.jpg) bottom center no-repeat;
    letter-spacing: 1.2em;
}


For Controls, it's all really a bit of layout that doesn't affect functionality, so feel free to adjust.

src/components/Header/Header.css
.Title {
    width: 100%;
    height: 80px;
    float: left;
    text-align: center;
    border-bottom: 1px solid #DDDDDD;
    background: url(../../img/bg.jpg) bottom center no-repeat;
    letter-spacing: 1.2em;
}

.Controls {
    width: 100%;
    height: 70px;
    float: left;
}

.Controls div{
    width: 50%;
    margin-left: auto;
    margin-right: auto;
    text-align: center;
}


Finally, let's see Header in action. Looks good, right?




Now we will need to add actions to the controls. We have BtnStart_click() which sets started to true, along with resetting the variables remaining, result, usedQuestions and question to their default values. BtnEnd_click() is the Cancel operation, and it sets started to false, along with resetting the variables charset, answer, result, usedQuestions and question to their default values.

src/components/Header/Header.js
function Header(props) {
    let charset = props.charset;
    let setCharset = props.setCharset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let usedQuestions = props.usedQuestions;
    let setStarted = props.setStarted;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setResult = props.setResult;
    let setUsedQuestions = props.setUsedQuestions;
    let setQuestion = props.setQuestion;

    const BtnStart_click = ()=> {
        setStarted(true);
        setRemaining(maxRemaining);
        setResult(0);
        setUsedQuestions([]);
        setQuestion(null);
    };


    const BtnEnd_click = ()=> {
        setStarted(false);
        setCharset(null);
        setAnswer("");
        setResult(0);
        setUsedQuestions([]);
        setQuestion(null);
    };

    
    if (started) {


And here we add the events on these buttons.

src/components/Header/Header.js
if (started) {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <br />
                    <button onClick={ ()=>{BtnEnd_click();}}>
                        { remaining === 0 ? "RESTART ➤ " : "CANCEL ☓" }
                    </button>
                </div>
            </div>
        </>            
    );
} else {
    return (
        <>
            <div className="Title">
                <h1>J-Trainer</h1>
            </div>
            <div className="Controls">
                <div>
                    <input type="radio" name="charset"  /> Hiragana <br />
                    <input type="radio" name="charset" /> Katakana <br />
                    <br />
                    <button onClick={ ()=>{BtnStart_click();}} disabled={ charset === null ? true : false }>
                        BEGIN ➤
                    </button>
                </div>
            </div>
        </>
    );          
}   


However, since charset is still null, the Begin button will still be disabled. The user is supposed to set the appropriate value for charset by selecting either "Hiragana" or "Katakana". To do this, we make sure clicking on either one triggers the SetCurrentCharset() function with either "hiragana" or "katakana" passed in as an argument.

src/components/Header/Header.js
<input type="radio" name="charset" onClick={()=>{SetCurrentCharset('hiragana');}}  /> Hiragana <br />
<input type="radio" name="charset" onClick={()=>{SetCurrentCharset('katakana');}} /> Katakana <br />


Then we define SetCurrentCharset(). It sets charset using its setter, which in turns calls GetCharset(), passing in name as an argument.

src/components/Header/Header.js
const BtnEnd_click = ()=> {
    setStarted(false);
    setCharset(null);
    setAnswer("");
    setResult(0);
    setUsedQuestions([]);
    setQuestion(null);
};

const SetCurrentCharset = (name)=> {
    setCharset(GetCharset(name));
}


if (started) {


This function needs to be defined. Why we're going about it in such a roundabout way, is because it will be used later on during testing, and we want to make life easier for ourselves. So add this import statement.

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

import GetCharset from '../../utils/GetCharset';

function Header(props) {


We will create the subdirectory, utils, inside the directory src. Then inside the utils directory, we create GetCharset.js.

In this file, we define GetCharset() as a function that has a parameter, setName. After that, we export it.

src/utils/GetCharset.js
const GetCharset = (setName) => {

}

export default GetCharset;    


In the function, we define alphabet as an empty array by default. If setName is "hiragana" or "katakana", we will change the value of alphabet.

src/utils/GetCharset.js
const GetCharset = (setName) => {
    let alphabet = [];

    if (setName === "hiragana") {
        alphabet = [

        ];
    }

    if (setName === "katakana") {
        alphabet = [

        ];      
    }

    return alphabet;

}

export default GetCharset;    


In the case of "hiragana", I have added in the first five elements. Each element is an object that has the properties char and romaji. char stores the Japanese character in that alphabet, and romaji is that character's pronounciation.

src/utils/GetCharset.js
const GetCharset = (setName) => {
    let alphabet = [];

    if (setName === "hiragana") {
        alphabet = [
            {"char": "あ", "romaji": "a"},
            {"char": "い", "romaji": "i"},
            {"char": "う", "romaji": "u"},
            {"char": "え", "romaji": "e"},
            {"char": "お", "romaji": "o"},

        ];
    }

    if (setName === "katakana") {
        alphabet = [

        ];      
    }

    return alphabet;
}

export default GetCharset;    


Here's the entire set!

src/utils/GetCharset.js
const GetCharset = (setName) => {
    let alphabet = [];

    if (setName === "hiragana") {
        alphabet = [
            {"char": "あ", "romaji": "a"},
            {"char": "い", "romaji": "i"},
            {"char": "う", "romaji": "u"},
            {"char": "え", "romaji": "e"},
            {"char": "お", "romaji": "o"},
            {"char": "か", "romaji": "ka"},
            {"char": "き", "romaji": "ki"},
            {"char": "く", "romaji": "ku"},
            {"char": "け", "romaji": "ke"},
            {"char": "こ", "romaji": "ko"},
            {"char": "さ", "romaji": "sa"},
            {"char": "し", "romaji": "shi"},
            {"char": "す", "romaji": "su"},
            {"char": "せ", "romaji": "se"},
            {"char": "そ", "romaji": "so"},
            {"char": "た", "romaji": "ta"},
            {"char": "ち", "romaji": "chi"},
            {"char": "つ", "romaji": "tsu"},
            {"char": "て", "romaji": "te"},
            {"char": "と", "romaji": "to"},
            {"char": "な", "romaji": "na"},
            {"char": "に", "romaji": "ni"},
            {"char": "ぬ", "romaji": "nu"},
            {"char": "ね", "romaji": "ne"},
            {"char": "の", "romaji": "no"},
            {"char": "は", "romaji": "ha"},
            {"char": "ひ", "romaji": "hi"},
            {"char": "ふ", "romaji": "fu"},
            {"char": "へ", "romaji": "he"},
            {"char": "ほ", "romaji": "ho"},
            {"char": "ま", "romaji": "ma"},
            {"char": "み", "romaji": "mi"},
            {"char": "む", "romaji": "mu"},
            {"char": "め", "romaji": "me"},
            {"char": "も", "romaji": "mo"},        
            {"char": "や", "romaji": "ya"},
            {"char": "ゆ", "romaji": "yu"},
            {"char": "よ", "romaji": "yo"},
            {"char": "ら", "romaji": "ra"},
            {"char": "り", "romaji": "ri"},
            {"char": "る", "romaji": "ru"},
            {"char": "れ", "romaji": "re"},
            {"char": "ろ", "romaji": "ro"},
            {"char": "わ", "romaji": "wa"},
            {"char": "を", "romaji": "wo"},
            {"char": "ん", "romaji": "n"},
            {"char": "が", "romaji": "ga"},
            {"char": "ぎ", "romaji": "gi"},
            {"char": "ぐ", "romaji": "gu"},
            {"char": "げ", "romaji": "ge"},
            {"char": "ご", "romaji": "go"},
            {"char": "ざ", "romaji": "za"},
            {"char": "じ", "romaji": "ji"},
            {"char": "ず", "romaji": "zu"},
            {"char": "ぜ", "romaji": "ze"},
            {"char": "ぞ", "romaji": "zo"},
            {"char": "だ", "romaji": "da"},
            {"char": "ぢ", "romaji": "ji"},
            {"char": "づ", "romaji": "zu"},
            {"char": "で", "romaji": "de"},
            {"char": "ど", "romaji": "do"},
            {"char": "ば", "romaji": "ba"},
            {"char": "び", "romaji": "bi"},
            {"char": "ぶ", "romaji": "bu"},
            {"char": "べ", "romaji": "be"},
            {"char": "ぼ", "romaji": "bo"},
            {"char": "ぱ", "romaji": "pa"},
            {"char": "ぴ", "romaji": "pi"},
            {"char": "ぷ", "romaji": "pu"},
            {"char": "ぺ", "romaji": "pe"},
            {"char": "ぽ", "romaji": "po"}

        ];
    }

    if (setName === "katakana") {
        alphabet = [
            {"char": "ア", "romaji": "a"},
            {"char": "イ", "romaji": "i"},
            {"char": "ウ", "romaji": "u"},
            {"char": "エ", "romaji": "e"},
            {"char": "オ", "romaji": "o"},    
            {"char": "カ", "romaji": "ka"},
            {"char": "キ", "romaji": "ki"},
            {"char": "ク", "romaji": "ku"},
            {"char": "ケ", "romaji": "ke"},
            {"char": "コ", "romaji": "ko"},
            {"char": "サ", "romaji": "sa"},
            {"char": "シ", "romaji": "shi"},
            {"char": "ス", "romaji": "su"},
            {"char": "セ", "romaji": "se"},
            {"char": "ソ", "romaji": "so"},
            {"char": "タ", "romaji": "ta"},
            {"char": "チ", "romaji": "chi"},
            {"char": "ツ", "romaji": "tsu"},
            {"char": "テ", "romaji": "te"},
            {"char": "ト", "romaji": "to"},
            {"char": "ナ", "romaji": "na"},
            {"char": "ニ", "romaji": "ni"},
            {"char": "ヌ", "romaji": "nu"},
            {"char": "ネ", "romaji": "ne"},
            {"char": "ノ", "romaji": "no"},            
            {"char": "ハ", "romaji": "ha"},
            {"char": "ヒ", "romaji": "hi"},
            {"char": "フ", "romaji": "fu"},
            {"char": "ヘ", "romaji": "he"},
            {"char": "ホ", "romaji": "ho"},
            {"char": "マ", "romaji": "ma"},
            {"char": "ミ", "romaji": "mi"},
            {"char": "ム", "romaji": "mu"},
            {"char": "メ", "romaji": "me"},
            {"char": "モ", "romaji": "mo"},        
            {"char": "ヤ", "romaji": "ya"},
            {"char": "ユ", "romaji": "yu"},
            {"char": "ヨ", "romaji": "yo"},
            {"char": "ラ", "romaji": "ra"},
            {"char": "リ", "romaji": "ri"},
            {"char": "ル", "romaji": "ru"},
            {"char": "レ", "romaji": "re"},
            {"char": "ロ", "romaji": "ro"},
            {"char": "ワ", "romaji": "wa"},
            {"char": "ヲ", "romaji": "wo"},
            {"char": "ン", "romaji": "n"},
            {"char": "ガ", "romaji": "ga"},
            {"char": "ギ", "romaji": "gi"},
            {"char": "グ", "romaji": "gu"},
            {"char": "ゲ", "romaji": "ge"},
            {"char": "ゴ", "romaji": "go"},
            {"char": "ザ", "romaji": "za"},
            {"char": "ジ", "romaji": "ji"},
            {"char": "ズ", "romaji": "zu"},
            {"char": "ぜ", "romaji": "ze"},
            {"char": "ゾ", "romaji": "zo"},
            {"char": "ダ", "romaji": "da"},
            {"char": "デ", "romaji": "de"},
            {"char": "ド", "romaji": "do"},
            {"char": "バ", "romaji": "ba"},
            {"char": "ビ", "romaji": "bi"},
            {"char": "ブ", "romaji": "bu"},
            {"char": "べ", "romaji": "be"},
            {"char": "ボ", "romaji": "bo"},
            {"char": "パ", "romaji": "pa"},
            {"char": "ピ", "romaji": "pi"},
            {"char": "プ", "romaji": "pu"},
            {"char": "ペ", "romaji": "pe"},
            {"char": "ポ", "romaji": "po"}

        ];      
    }

    return alphabet;
}

export default GetCharset;    


So now if you select either "Hiragana" or "Katakana", the button should be enabled.




And then if you click "BEGIN", you should get a "CANCEL" button. And if you click "CANCEL", it brings you right to the beginning.




The Display component

Now we will expand on what we just did, with the Display component. We begin with an additional import statement in App.js.

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

function App() {


We will then add the component in the div that is styled using the CSS class Middle.

src/App.js
<div className="Top">
    <Header
      charset={ charset }
      started={ started }
      remaining={ remaining }
      usedQuestions={ usedQuestions }
      maxRemaining={ maxRemaining }
      setStarted={ setStarted }
      setCharset={ setCharset }
      setRemaining={ setRemaining }
      setAnswer={ setAnswer }
      setResult={ setResult }
      setQuestion={ setQuestion }
      setUsedQuestions={ setUsedQuestions }
    />
</div>
<div className="Middle">
    <Display
      charset={ charset }
      started={ started }
      remaining={ remaining }
      maxRemaining={ maxRemaining }
      answer={ answer }
      question={ question }
      usedQuestions={ usedQuestions }
      result={ result }
      setStarted={ setStarted }
      setRemaining={ setRemaining }
      setAnswer={ setAnswer }
      setQuestion={ setQuestion }
      setUsedQuestions={ setUsedQuestions }
    />

</div>


As before, we create the Display directory inside components, and create Display.js, Display.css and index.js. Leave Display.css alone; for now, it's enough that the file exists. But we will have to do ths for index.js.

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


For Display.js, we import the CSS file, define the function Display(), and have the export statement at the end.

src/components/Display/Display.js
import './Display.css';

function Display(props) {

}

export default Display;


Declare and initialize the variables according to the properties that were passed down.

src/components/Display/Display.js
function Display(props) {
    let charset = props.charset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let answer = props.answer;
    let question = props.question;
    let result = props.result;
    let usedQuestions = props.usedQuestions;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setQuestion = props.setQuestion;
    let setUsedQuestions = props.setUsedQuestions;

}


By default, we return this. These are just app instructions. See how the CSS class btnSmall is used!

src/components/Display/Display.js
function Display(props) {
    let charset = props.charset;
    let started = props.started;
    let remaining = props.remaining;
    let maxRemaining = props.maxRemaining;
    let answer = props.answer;
    let question = props.question;
    let result = props.result;
    let usedQuestions = props.usedQuestions;
    let setRemaining = props.setRemaining;
    let setAnswer = props.setAnswer;
    let setQuestion = props.setQuestion;
    let setUsedQuestions = props.setUsedQuestions;
    
    return (
        <>
            <br />
            <h3>Welcome to J-Trainer!</h3>
            <ol>
                <li>Select your character set.</li>
                <li>Click <button className="btnSmall">BEGIN ➤ </button> to start.</li>
                <li>For every character that is displayed, select the correct pronounciation. Note that some characters may have the same pronounciation as others.</li>
                <li>Click the <button className="btnSmall">REMAINING ➤ </button> button to continue after the results are displayed.</li>
            </ol>
        </>            
    );

}


This is the default view for the Display component. No matter what you click right now, this is what you will see.




Of course, there will be more done here. There is way more to the Display component than has been covered here, and we will get to it!

Next

We will focus on the keyboard and display.

Wednesday, 15 June 2022

Ten Cheap Shots At Internet Explorer

It's happening today. 15th June 2022. This is when Internet Explorer stops being supported by Microsoft. It started back in 2015, when I celebrated the impending demise of Internet Explorer, so I won't rehash just how much I hate this botched abortion of a web browser.

Suffice to say, my life as a web developer has been improved immeasurably by no longer having to support Internet Explorer. I know I'm not alone in this, if the proliferation of memes making fun of Internet Explorer is any indication. To commemorate the great event this month, here are a few of my favorites.

1. Keep calm and...?


Yikes, so ugly.

This is a dig at Internet Explorer's butt-ugly interface for missing images. Though, back in the days when its main competition was Netscape Navigator, this was already a marked improvement. By today's standards, though, bleh.

2. No bugs!

Don't open the browser.

Technically, that's true. Kind of like saying shit doesn't stink if you don't sniff it, but you get the idea.

3. The perfect user


Turtle power!

Probably sums up Internet Explorer's speed issues.

4. Still faster


#foreveralone

Yep another cheap shot at Internet Explorer's speed.

5. Browsers!


We all know a
guy like that.

And yet another one.

6. Internet Explorer is useful for one thing...


Yep!


That's an often-used joke, that the best use of Internet Explorer is to download other browsers.

7. Ask her out


Grow some IE-sized balls.

Internet Explorer asking to be your main browser. The audacity!

8. Guns


Shoot yourself.

This one made me choke on my morning coffee. It's so, so apt.

9. Hidden folder hack


This is genius.

Brilliant. No one would touch that, ever.

10. Last and not least...


The worst!

Yep. Just fucking die already, Internet Explorer.

Death to Internet Explorer!

Yep. Go to hell, you bloated misbegotten excuse for software. You won't be missed!

Rest in pieces,
T___T

Friday, 10 June 2022

Different Number Types in Datasets

Data encountered in Data Analytics may be numerical - and often it is important to understand what kind of numerical data it is. Sometimes the data just happens to be a number, but may actually represent something other than an actual numerical value.

Today, I will attempt to provide an understanding of the different kinds of numerical data.

Quantitifiable

These are actual numerical values. They represent quantities and measures. Number of apples collected per season from an orchard. Weekend ticket sales for newly premiering movies. Number of people incarcerated in 2021.

Quantifiable numbers.

Such values can be further categorized into discrete and continuous values, and I have already gone through that in a previous blogpost. Suffice to say, these values have quantifiable meaning. They can have calculations performed on them - summation, averages and such - to derive more data.

Categorical

Categorical values are numbers that don't represent numerical values. They could have numerical functions performed on them, but the end results would have no discernable meaning. The numbers are actually representations of other values. Such as serial numbers, barcodes or foreign key fields.

Just a label, like a
serial number.

Take, for instance, a 1 or 0 representing true or false, Male or Female. Or, for a more extensive example, let's take a table of different sports. If 1 represents basketball, 2 represents football, 3 represents baseball and 4 represents hockey, getting the mean of all these numbers in the table would net you a result that made no sense whatsoever.

Ordinal

If you combined the best parts of Categorical and Quantifiable values, it would look a lot like Ordinal values. Ordinal values are numbers that are representations of other values that are also quantities and measures.

Approval ratings
can be ordinal.

A good example would be ratings on a form. Let's say, for instance, the question on the poll was "On a scale of 1 to 10, with 10 being excellent and 1 being dreadful, how would you rate our service?", the numbers would represent different ratings, but we could still have a Mean or Median derived to provide an impression of the general approval rating.

In conclusion

It is important, when dealing with numerical values, or even just values that look like numbers, to understand just what these numbers represent. They may not mean what you think they mean, and this could lead to some horrific data crunching.

Much numerous regards!
T___T

Saturday, 4 June 2022

Thoughts About HTML5

Prior to 2010, much of the website work I did was in XHTML. It had its numerous quirks and all, but thinking back, it was all really a huge mess. Conflicts in standards, lack of consistency and a whole bunch of extra JavaScript I had to write in order for shit to work.

And then HTML5 came along.


HTML5 FTW!

I won't claim that it was love at the first sight, but once major browsers started getting their act together, things rapidly began falling into place. Today, I will be listing some of the various ways HTML5 has improved the web development experience of many web developers around the world.

Less code, more functionality

The days of me needing to write code to validate basic things in HTML were over. The type attribute in HTML5 input tags took care of all that. Things like the placeholder attribute also resulted in me not needing to write code for that particularly basic piece of functionality. And not to mention date widgets!

Fields have more functionality.

This has resulted in a lot less code to maintain over various applications, while preserving the level of functionality, especially in HTML forms. From a programming point of view, you can't go wrong with getting more for less.

Syntax simplicity and consistency

Remember the days of declaring document headers for XHTML? That was a pain in the arse.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">


Now we have this...
<!doctype HTML>


...and come on, there's almost no way to screw that up.

HTML5 also came with the standard of making almost everything lowercase, and ensuring that every opening tag had a closing tag (with some tags being self-closing). It deprecated useless tags whose effects could have been replicated using CSS.

The plethora of new tags introduced such as the ones for article, aside and so on, were a challenge to remember. But they actually make the HTML easier to read than if everything was a div or a span tag. Multimedia no longer needed an object tag with multiple qualifiers and we could simply specify an audio or video tag.

Web media.

And if we still required custom data, all we needed was something like data-customNameHere in a tag and we could just put in values as needed. This led to so much easier programming. All we had to do later was retrieve the data from a convenient data structure!

API Features

There are some entirely new API features in HTML5 such as the Canvas, Drag And Drop and Web Storage options. There are more, of course, but my limited usage of these have not exposed me to them as of yet.

Drag and drop.

This made past challenges such as caching, easier to manage. And, as mentioned earlier, this has also resulted in less code needing to be written for these widely-used features.

Happy coding!

It's been almost 15 years since HTML5 entered the web industry. I remember the bad old days prior to its existence. Some with rose-tinted glasses and fond nostalgia, and some with shudders. I'm still learning as I go along. There is so much left to cover.

HTML5 has been a boon to the industry. Long may it live, at least till it outlives its usefulness!

<bye />
T___T

Wednesday, 1 June 2022

App Review: Bazooka Boy

There are games that are so well-crafted that they grab your attention for a good bit, there are games which you ditch almost right away after installing, and there are games that fall into neither category - such as Bazooka Boy by Supersonic Studios Ltd.


This game is a cheap knock-off of the classic Angry Birds, and it follows the same structure - you have a certain number of shots and your goal is to eliminate every enemy on the screen.

The Premise

At the beginning of every stage, you have three shots with whatever weapon you choose. There are enemies on screen, all of whom you have to kill, either via a direct hit, by causing debris to fall on them, or by causing them to fall to their deaths. In the process, you have to avoid killing yourself via a stray shot or running out of shots.



There are a whole bunch of different weapons, each with their own in-game physics and capabilities. Their power is limited by context - in some environments, some weapons are simply more effective than others. For instance, if all your enemies are arranged in a straight line, the Laser would take them all out in one hit. If your enemies are in staggered locations or covered under a structure, you may want a more surgical solution such as the Driller or the Digger.


The environment is made out of a variety of building blocks.



Basic wooden beams. Imminently destructible, but beware of flying debris!


Stone blocks. Indestructible and a potential hazard.


Rubber blocks. Indestructible, and your missiles bounce off them. Could lead to interesting solutions and utter pandemonium!


Fuel drums. An explosive hazard.


Moving platforms. Adds a level of complexity.


Teleport portals. They come in pairs. Anything that goes into one portal goes out the other. Oh, the chaos!


The Aesthetics

This one merits a solid meh.

Bazooka Boy isn't ugly by any means, but the graphics are really quite simple. It's mostly basic geometric shapes and colors.

The Experience

Solid fun. I really don't know why that is the case, but despite everything I didn't like about it (and there was plenty, I assure you) it still kept me playing. What sorcery is this?!

The Interface

Tap and drag to position the shot. Release to fire. The controls are simple enough. The only problem is that sometimes you may accidentally fire off when you didn't mean to. Other functions, such as changing weapons, are straightforward.

What I liked

Strategic elements. Where you aim for, what weapon you use, when it's done - all this is a factor in deciding whether or not you succeed. And possibly, how well you succeed. There is also a certain element of luck, as sometimes something that should not have worked, turns out to work simply because of a stray bounce from a struck object. The reverse happens as well.


Blood spurting and explosions. The gleeful way that Bazooka Boy approaches violence, is a joy to behold. Honestly, it's so thrilling to just watch an explosion launch an enemy sky-high, and get pelted by stray shrapnel on the way down. And when he finally lands, gets immediately blown into a deeper hole by a landing bomb.



Dressing up your Avatar. The outfit are cheesy, yes. It's also kind of fun.


Challenges. This is a neat touch. You have extra-tricky stages that you have to win with only one shot.

What I didn't

Graphical glitches. Things don't always wore smoothly in Bazooka Boy. The ground may suddenly rise up, pillars may increase in size, and even float in mid-air after their foundations are destroyed.

Inconsistent physics. Explosions can be set off by hitting fuel drums. And sometimes they don't get triggered. Explosions may cause enemies to fly upwards, and sometimes sideways. This can have serious effects on your strategy.

Repeating stages. After a certain point, stages start getting repeated. To be fair, it would be unrealistic to expect an infinite number of unique stages, but a repetition once every five stages is ridiculous.

Could be improved by asking the user if they want to try again for a better score. You can take up to three shots to achieve your target, but sometimes you want to see if you can do it in one. The game just moves on when the target is achieved, without giving the user an option for retrying.

The game keeps cutting to Lazada. This is a huge one. Ads between stages are one thing, but if you're in the middle of taking a shot and end up in Lazada, it's one heck of an annoyance.

Sometimes the stage is over before it begins. Fuel drums explode right away without you doing anything. Stone blocks come crashing down on your avatar as soon as the environment loads. What the hell, Bazooka Boy?!

Conclusion

Bazooka Boy isn't great - that distinction is reserved for more deserving works - but at the same time, it is simplistic bloody entertainment. It can be a really decent time-sucker if you allow it to be. This game is a long way from being perfect, or even good. It certainly has no business occupying my interest for as long as it has.

My Rating

5.5 / 10

Oh, shoot!
T___T