DEV Community

Cover image for 13. Refactoring and data filling
Rustam Apay
Rustam Apay

Posted on

13. Refactoring and data filling

When you write code initially, it is like a draft. You do it fast and your only goal is to make it working and to see how things fits to each other. After a while you do a revision of your code, and edit it, making it clearer, shorter, more reusable, split it into logical modules.

It is good to move out from components everything without this to some modules (utils). Another hint for you is a length of file: a module with more than 100 lines is an applicant to been split.

If you look at our project file structure, you'll notice, that there are main code and assets -- additional files that we read and use from components, e.g. keyboard data files and audios.

It is good to move functionality related to external files into separate module. Maybe we decide to change our file structure in the future, and it will be easy to rewrite paths to files in one place, instead of seeking for them in all components.

Functions that work with assets

getAudioFileName and getKeyLabels

If you remember, on top of Keyboard.js and Key.js we placed functions, that aren't part of components. Create in the project root directory a new file utils.js and move them there. "Move" means cut (copy and delete ctrl+x) from components, and paste to utils.js).

utils.js

export const getAudioFileName = (keyContent, shiftKey) => {
    ...
}

export const getKeyLabels = keyContent => {
    ...
}
Enter fullscreen mode Exit fullscreen mode

Write export before each function, so we can import them from other files.

On top of Keyboard.js add line:

import { getAudioFileName } from '../utils.js'
Enter fullscreen mode Exit fullscreen mode

On top of Key.js add line:

import { getKeyLabels } from '../utils.js'
Enter fullscreen mode Exit fullscreen mode

Code of 2 components became clearer.

Open the app. It should work as before.

loadKeyboardData

Look at Keyboard method getKeyboardData:

Keyboard.js

async getKeyboardData(lang) {
    const { default: keyboardData } = await import(
        `../keyboardData/${lang}.js`
    )
    this.keyboardData[lang] = keyboardData
}
Enter fullscreen mode Exit fullscreen mode

There is an operation with an asset file -- keyboard data. Let's move it to utils.

Add to utils.js a new function:

utils.js

export const loadKeyboardData = async lang => {
    const { default: keyboardData } = await import(`../keyboardData/${lang}.js`)

    return keyboardData
}
Enter fullscreen mode Exit fullscreen mode

In Keyboard.js import it, and use inside getKeyboardData method:

Keyboard.js

import { getAudioFileName, /* add:  */loadKeyboardData } from '../utils.js'
...
methods: {
    ...
    async getKeyboardData(lang) {
                this.keyboardData[lang] = await loadKeyboardData(lang)
            },
    ...
}
Enter fullscreen mode Exit fullscreen mode

Now we get keyboard data in loadKeyboardData, so the name getKeyboardData isn't intuitive as before. Let's rename it to setKeyboardData. In Keyboard.js find/replace old name with the new one (ctrl+h, Replace All). There were only 3 matches.

Open the app, it should work as before.

playKeyAudio

In Keyboard.js method playKey look at function playKeyAudio.

Keyboard.js

const playKeyAudio = (lang, code, shiftKey) => {
    const keyContent = this.getKeyContent(lang, code)
    const fileName = getAudioFileName(keyContent, shiftKey)
    const audio = new Audio(`../keyboardData/${lang}/${fileName}.mp3`)
    return audio.play()
}
Enter fullscreen mode Exit fullscreen mode

It also has a link to an external file, so let's move it to utils.js.

In utils.js there is no this, because this is a component itself, so we should rewrite playKeyAudio without this.

utils.js

export const playKeyAudio = (lang, keyContent, shiftKey) => {
    const fileName = getAudioFileName(keyContent, shiftKey)
    const audio = new Audio(`../keyboardData/${lang}/${fileName}.mp3`)
    return audio.play()
}
Enter fullscreen mode Exit fullscreen mode

In top of Keyboard.js import this new function, and remove getAudioFileName import, because we use it now only in utils.

Keyboard.js

import { playKeyAudio, loadKeyboardData } from '../utils.js'
...
methods: {
    ...
    playKey(keyContent) {
        const { code } = keyContent
        const { shiftKey, currentLang } = this

        playKeyAudio(currentLang, keyContent, shiftKey).catch(() => {
            // fallback
            if (this.currentLang !== 'en') {
                const keyContent = this.getKeyContent('en', code)
                playKeyAudio('en', keyContent, shiftKey)
            }
        })
    },
}
Enter fullscreen mode Exit fullscreen mode

Open your app. It should work as before.

Conclusion

By moving all functions working with external files (assets) we achieved:

  1. clarity of components -- they became shorter and clearer
  2. flexibility of the whole project.

E.g. now we can change file structure of the project easily, because every link to assets are placed in one module. We can add new assets into game rapidly, develop advanced methods to work with them etc. It is easy to do, because all logic related to assets are placed in one place.

Diffs in code 13.1

Change file structure

In the folder keyboardData create a folder sounds and move there en, ru, ar folders.

In utils for path in playKeyAudio add /sounds/:

utils.js

const audio = new Audio(`../keyboardData/sounds/${lang}/${fileName}.mp3`)
Enter fullscreen mode Exit fullscreen mode

Open the app. It should work as before.

Despite the fact that we have changed file structure of our project, we are not afraid that we forget to change some path somewhere in components. Because every functions that use files are placed compactly in one module.

Diffs in code 13.2

getKeyContent

getKeyContent looks like something working outside components, even thought it doesn't work with assets. It does universal calculation for any keyboardData and key code. Let's move it to utils, and extend its functionality:

utils.js

export const getKeyContent = ({ keyboardData, code = '', value = '' }) => {
    return keyboardData.flat().find(elem => {
        const { main, shifted } = elem
        return elem.code === code || value === main || value === shifted
    })
}
Enter fullscreen mode Exit fullscreen mode

We replaced argument lang with keyboardData to remove this from the function. Now, before every call of getKeyContent, we should rewrite code using lang and this to calculate keyboardData.

We replaced set of arguments (lang, code) with the object { keyboardData, code, ... } -- to not care about their ordering anymore.

We added a new argument value - to find keyContent not only by its code, but also by its value.

And we specified default values for code and value: { code = '', value = '' } to prevent errors when only one of them is specified.

Let's rewrite Keyboard using new version of getKeyContent.

Keyboard.js first lines

import { playKeyAudio, loadKeyboardData, /* add: */ getKeyContent } from '../utils.js'
...
Enter fullscreen mode Exit fullscreen mode

Find (ctrl+f) all getKeyContent matches in Keyboard.js, and replace it with the rewritten imported function, with new arguments:

Keyboard.js mounted

/* replace: 
const keyContent = this.getKeyContent(this.currentLang, code)
with next 2 lines */
const keyboardData = this.keyboardData[this.currentLang]
const keyContent = getKeyContent({ keyboardData, code })
Enter fullscreen mode Exit fullscreen mode

Keyboard.js methods playKey

/* replace: 
const keyContent = this.getKeyContent('en', code)
with next 2 lines */
const keyboardData = this.keyboardData['en']
const keyContent = getKeyContent({ keyboardData, code })
Enter fullscreen mode Exit fullscreen mode

Now we have the universal advanced function to work with keyboardData, that we can improve easily in the future. We will use it chapter 15 to add new feature to the app.

Open the app. It should work as before.

Diffs in code 13.3

Remove unnecessary code

In index.html remove all commented code inside <body> tag. We don't need it anymore.

index.html

<body>
    <div id="app">{{ message }}</div>
    <script src="./index.js" type="module"></script>
</body>
Enter fullscreen mode Exit fullscreen mode

In App.js remove from the template first line App-{{currentLang}}.

In Keyboard.js remove from the template first 2 lines:

<div>activeKey: {{activeKey}}</div>
<div>shiftKey: {{shiftKey}}</div>
Enter fullscreen mode Exit fullscreen mode

Diffs in code 13.4

Data filling

The joyful moment has come to see the entire keyboard.

Download the project repo archive and unzip it. Remove folder keyboardData from your project. Copy folder keyboardData from the downloaded project and put it to your project root folder. In other words -- replace your old incomplete data with the new complete data.

Result

3 language keyboards with 6 rows and 80 keys should work just like before our 3 test rows with 15 keys worked.

Maintainable, extensible

Notice, that adding new data didn't affect on our main code base. We didn't do any extra steps to make the app working. It just works itself, because we separated main code from data.

It's been said, that our app is:

  • maintainable
  • extensible

If someone tomorrow will send to us a new keyboard data: Japanese, Hebrew, Georgian, Armenian, Hindi, Thai... We can upgrade our app in 1 minute. In other words: we can maintain and extend the app easily.

Diffs in code 13.5

Entire code after the chapter 13

Top comments (0)