DEV Community

Gohomewho
Gohomewho

Posted on

table: refactor our code

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 = []) { /**/ }
Enter fullscreen mode Exit fullscreen mode

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 = []) { 
  // ...
}
Enter fullscreen mode Exit fullscreen mode

import makeColumnsSwappable in main.js

import { makeColumnsSwappable } from "./makeColumnsSwappable.js"
// ...

// function makeColumnsSwappable is removed from main.js
Enter fullscreen mode Exit fullscreen mode

I thought this would instantly work. Test it and got an error.

Uncaught ReferenceError: copyElementStyleToAnother is not defined.
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"
Enter fullscreen mode Exit fullscreen mode

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) { /**/ }
Enter fullscreen mode Exit fullscreen mode

Import createTable in main.js.

import { createTable } from "./createTable.js"
import { makeColumnsSwappable } from "./makeColumnsSwappable.js"
// ...
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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() {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

main.css

@import './makeColumnsSwappable.css';
@import './myTables.css';
Enter fullscreen mode Exit fullscreen mode

index.html
change style.css to main.css

<link rel="stylesheet" href="./main.css">
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)