table: refactor our code
Gohomewho
Posted on December 19, 2022
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 we have so far.
Review our code
Before we start refactoring, let's quickly review what we have done.
// 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.
// add a export statement, so we can import it in other file
export function makeColumnsSwappable(columnsContainer, elementsToPatch = []) {
// ...
}
import makeColumnsSwappable
in main.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.
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
// 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.
// 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
.
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.
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. We can also refactor it. Move everything related to create table into createJSONPlaceholderTable
function.
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.
// 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
.
// 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.
// 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
.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
.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
@import './makeColumnsSwappable.css';
@import './myTables.css';
index.html
change style.css
to main.css
<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!
Posted on December 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.