table: refactor our code
Tags: tutorial, javascript, beginners, webdev
Published: 2022-12-19 15:13:04

We have been developing everything inside a single file `main.js` through four series. Two main features we developed are `createTable` and `makeColumnsSwappable`. In the last three series, we almost never touch the code we wrote in the first series. Although our code is not clutter, it is still a good time to refactor, so we can separate the concern.


The [code](https://github.com/ga676005/table/tree/8fbc0bae304772ef02e8c6c9eb65fb1edab361cd) we have so far.

## Review our code
Before we start refactoring, let's quickly review what we have done. 
```js
// we created `helpers.js` in the last series 
// and put `copyElementStyleToAnother` there
import { copyElementStyleToAnother } from "./helpers.js"

// dummy data copied from https://jsonplaceholder.typicode.com/
const users = [/*...*/]

// the columns of `users` we want to show in the table
const nameOfDefaultColumns = [/*...*/]

// a mapper to process complex data of columns
const columnFormatter = {/*...*/}

// use information above to create our table
const table = createTable(nameOfDefaultColumns, users, columnFormatter)

// add a class to table for styling
table.classList.add('my-table')

// add table into DOM, so it can show up in the page
document.body.appendChild(table)

// get the column elements and make them swappable
const columnsContainer = table.querySelector('thead').firstElementChild
const elementsToPatch = table.querySelectorAll('tbody > tr')
makeColumnsSwappable(columnsContainer, elementsToPatch)

// the main function to create a table
function createTable(columns, dataList, columnFormatter) { /**/ }

// part of `createTable`
function createTableHead(columns) { /**/ }

// part of `createTable`
function createTableBody(columns, dataList, columnFormatter) { /**/ }

// part of `createTableBody`
function createTableRow(columns, dataOfTheRow, columnFormatter) { /**/ }

// the main function to make columns swappable
function makeColumnsSwappable(columnsContainer, elementsToPatch = []) { /**/ }
```

---


## Start with the easiest
There are many places related to `createTable`. It might be scary to refactor it first. So let's first work on `makeColumnsSwappable` which only appears twice in our code. One is where we define the function, and the other one is where we use that function. 

Create a file `makeColumnsSwappable.js` and move `makeColumnsSwappable` to there.
```js
// add a export statement, so we can import it in other file
export function makeColumnsSwappable(columnsContainer, elementsToPatch = []) { 
  // ...
}
```

import `makeColumnsSwappable` in main.js
```js
import { makeColumnsSwappable } from "./makeColumnsSwappable.js"
// ...

// function makeColumnsSwappable is removed from main.js
```
I thought this would instantly work. Test it and got an error.

Uncaught ReferenceError: copyElementStyleToAnother is not defined.
![copyElementStyleToAnother is not defined](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hrnikg701rafjf9d0mt.png)
That's because `copyElementStyleToAnother` is used inside `makeColumnsSwappable`, and currently there is no `copyElementStyleToAnother` in `makeColumnsSwappable.js`.

Since `copyElementStyleToAnother` is only used inside `makeColumnsSwappable`, we can move its import statement from `main.js` to `makeColumnsSwappable.js`
```js
// move from `main.js` to `makeColumnsSwappable.js`
import { copyElementStyleToAnother } from "./helpers.js"
```  
The code should work again.

---


## Have confident to refactor more
Nice! We have done a warm up, let's move on to the table's code. `createTable` is the main function to create table. Similar to `makeColumnsSwappable`, it is a standalone feature that can be moved to a dedicated file. Create a `createTable.js` and move the code related to `createTable` to there.
```js
// move these from `main.js` to `createTable.js`

// the main function to create a table
// add a export statement, so we can import it in other file
export function createTable(columns, dataList, columnFormatter) { /**/ }

// part of `createTable`
function createTableHead(columns) { /**/ }

// part of `createTable`
function createTableBody(columns, dataList, columnFormatter) { /**/ }

// part of `createTableBody`
function createTableRow(columns, dataOfTheRow, columnFormatter) { /**/ }
```

Import `createTable` in `main.js`.
```js
import { createTable } from "./createTable.js"
import { makeColumnsSwappable } from "./makeColumnsSwappable.js"
// ...
```
The code should still work. 

The reason that we can simply move these code to separate files without breaking anything is because our functions don't rely on global variables or say external states. The variables that `createTable` or `makeColumnsSwappable` need are either created within them or passed through function's parameters.

Let's refactor the rest of the code in `main.js`. Here are what we left.
```js
import { createTable } from "./createTable.js"
import { makeColumnsSwappable } from "./makeColumnsSwappable.js"

// dummy data copied from https://jsonplaceholder.typicode.com/
const users = [/*...*/]

// the columns of `users` we want to show in the table
const nameOfDefaultColumns = [/*...*/]

// a mapper to process complex data of columns
const columnFormatter = {/*...*/}

// use information above to create our table
const table = createTable(nameOfDefaultColumns, users, columnFormatter)

// add a class to table for styling
table.classList.add('my-table')

// add table into DOM, so it can show up in the page
document.body.appendChild(table)

// get the column elements and make them swappable
const columnsContainer = table.querySelector('thead').firstElementChild
const elementsToPatch = table.querySelectorAll('tbody > tr')
makeColumnsSwappable(columnsContainer, elementsToPatch)
```

As we can see, the rest of the code are all related to create a table that uses the dummy data from [JSONPlaceholder](https://jsonplaceholder.typicode.com/). We can also refactor it. Move everything related to create table into `createJSONPlaceholderTable` function.
```js
import { createTable } from "./createTable.js"
import { makeColumnsSwappable } from "./makeColumnsSwappable.js"

const jsonPlaceholderTable = createJSONPlaceholderTable()
document.body.appendChild(jsonPlaceholderTable)

function createJSONPlaceholderTable() {
  const users = [/*...*/]
  const nameOfDefaultColumns = [/*...*/]
  const columnFormatter = {/*...*/}

  const table = createTable(nameOfDefaultColumns, users, columnFormatter)
  table.classList.add('my-table')

  const columnsContainer = table.querySelector('thead').firstElementChild
  const elementsToPatch = table.querySelectorAll('tbody > tr')
  makeColumnsSwappable(columnsContainer, elementsToPatch)

  return table
}
```

---


## Maybe no need to refactor
It might seem unnecessary to refactor `createJSONPlaceholderTable`. That's true because this table is the only thing in our application. It make more sense when we plan to add more tables, we can refactor like this and put them together.

Since this tutorial is about refactoring, let's keep going. Create a file `myTables.js` and move everything related to `createJSONPlaceholderTable` to there. 
```js
// move these from `main.js` to `myTables.js`
import { createTable } from "./createTable.js"
import { makeColumnsSwappable } from "./makeColumnsSwappable.js"

// add a export statement, so we can import it in other file
export function createJSONPlaceholderTable() {
  // ...
}
```

This is what we left in `main.js`.
```js
// don't forget to add .js at the end of "./myTables.js"
import { createJSONPlaceholderTable } from "./myTables.js"

const jsonPlaceholderTable = createJSONPlaceholderTable()
document.body.appendChild(jsonPlaceholderTable)
```
Test again the code should still work.

Let's refactor `createJSONPlaceholderTable` a little more. We can move the code that creates a swappable columns table to a function. So later when we have new data to create another table, we don't need to worry about how to put `createTable` and `makeColumnsSwappable` together.
```js
// myTables.js
import { createTable } from "./createTable.js"
import { makeColumnsSwappable } from "./makeColumnsSwappable.js"

// make a function that composite `createTable` and `makeColumnsSwappable`
// so we don't need to worry what elements to select
// and what to pass to `makeColumnsSwappable` when
// we want to create a new swappable columns table
function createColumnsSwappableTable(nameOfDefaultColumns, users, columnFormatter) {
  const table = createTable(nameOfDefaultColumns, users, columnFormatter)

  const columnsContainer = table.querySelector('thead').firstElementChild
  const elementsToPatch = table.querySelectorAll('tbody > tr')
  makeColumnsSwappable(columnsContainer, elementsToPatch)

  return table
}

export function createJSONPlaceholderTable() {
  const users = [/*...*/]
  const nameOfDefaultColumns = [/*...*/]
  const columnFormatter = {/*...*/}

  // change `createTable` to `createColumnsSwappableTable`
  // the arguments are the same as before
  const table = createColumnsSwappableTable(nameOfDefaultColumns, users, columnFormatter)
  // add things to this specific table
  table.classList.add('my-table')

  return table
}
```

We can also refactor CSS into files.
makeColumnsSwappable.css
```css
.column {
  cursor: move;
}

.columns-container:not(.moving) .column:hover {
  background-color: hsla(0, 0%, 77%, 0.5);
}

.column.moving {
  background-color: hsla(0, 0%, 60%, 0.5);
}
```

myTables.css
```css
.my-table,
.my-table th,
.my-table td {
  border: 1px solid lightgray;
  border-collapse: collapse;
  padding: 8px;
}

.my-table thead {
  transition: background-color .4s ease;
}

.my-table:hover thead {
  background-color: hsla(0, 0%, 87%, 0.5);
}
```

main.css
```css
@import './makeColumnsSwappable.css';
@import './myTables.css';
```

index.html
change `style.css` to `main.css`
```html
<link rel="stylesheet" href="./main.css">
```
Don't forget to test again that everything should still work.

After refactoring, if we want to create a new table, we go to `myTables.js`. If we want to add features to tables, we go to `createTable.js`. If we want to add features to swappable columns, we go to `makeColumnsSwappable.js`. We successfully separate the concern. If we want to develop new things, we can go to `main.js`, and refactor later like this.

---

One thing I want to point out is that if you are a beginner, don't worry about refactoring at all. Don't try to make code shorter. Don't ruin your logic. It something works, just let it be!