13. Refactoring and data filling
Tags: webdev, javascript, beginners, tutorial
Published: 2022-08-18 05:44:07

---
published: true
title: 13. Refactoring and data filling
tags: webdev, javascript, beginners, tutorial
cover_image: https://res.cloudinary.com/practicaldev/image/fetch/s--2Ycgb9E_--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7manuew9dwi5jlrf8p5.png
series: keyboard-learning-app
published_at: '2022-08-18T05:44:07Z'
---

- [Functions that work with `assets`](#functions-that-work-with-assets)
  - [`getAudioFileName` and `getKeyLabels`](#getaudiofilename-and-getkeylabels)
  - [`loadKeyboardData`](#loadkeyboarddata)
  - [`playKeyAudio`](#playkeyaudio)
  - [Conclusion](#conclusion)
- [Change file structure](#change-file-structure)
- [`getKeyContent`](#getkeycontent)
- [Remove unnecessary code](#remove-unnecessary-code)
- [Data filling](#data-filling)
- [Maintainable, extensible](#maintainable-extensible)

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

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

export const getKeyLabels = keyContent => {
	...
}
```

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

On top of `Keyboard.js` add line:

```js
import { getAudioFileName } from '../utils.js'
```

On top of `Key.js` add line:

```js
import { getKeyLabels } from '../utils.js'
```

Code of 2 components became clearer.

Open the app. It should work as before.

### `loadKeyboardData`

Look at `Keyboard` method `getKeyboardData`:

Keyboard.js

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

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

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

	return keyboardData
}
```

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

Keyboard.js

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

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

```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()
}
```

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

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

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

Keyboard.js

```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)
			}
		})
	},
}
```

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](https://github.com/ApayRus/keyboard/commit/7f64b04fdf035f9abc2b3b1af4f444dad32b0000)

## 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

```js
const audio = new Audio(`../keyboardData/sounds/${lang}/${fileName}.mp3`)
```

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](https://github.com/ApayRus/keyboard/commit/21b05cef1eb3ac154c568953a1001bb26f9ebcdd)

## `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

```js
export const getKeyContent = ({ keyboardData, code = '', value = '' }) => {
	return keyboardData.flat().find(elem => {
		const { main, shifted } = elem
		return elem.code === code || value === main || value === shifted
	})
}
```

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

```js
import { playKeyAudio, loadKeyboardData, /* add: */ getKeyContent } from '../utils.js'
...
```

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

Keyboard.js mounted

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

Keyboard.js methods `playKey`

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

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](https://github.com/ApayRus/keyboard/commit/0f653d981f7562fa54b52583154bbd847cf54cc6)

## Remove unnecessary code

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

index.html

```html
<body>
	<div id="app">{{ message }}</div>
	<script src="./index.js" type="module"></script>
</body>
```

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

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

```html
<div>activeKey: {{activeKey}}</div>
<div>shiftKey: {{shiftKey}}</div>
```

[Diffs in code 13.4](https://github.com/ApayRus/keyboard/commit/cc3ba26ef184cf609f44aba31e0802c3df810a2a)

## Data filling

The joyful moment has come to see the entire keyboard.

Download [the project repo archive](https://github.com/ApayRus/keyboard/archive/refs/heads/master.zip) 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

![](https://raw.githubusercontent.com/ApayRus/keyboard-trainer-tutorial/master/tutorial/images/Peek%202022-08-13%2006-38.gif)

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](https://github.com/ApayRus/keyboard/commit/47c1f8301a4b5111f84bb8aa3563d042f10cde85)

[Entire code after the chapter 13](https://github.com/ApayRus/keyboard/tree/13.-Refactoring-and-data-filling)