사용자가 카드를 추가하면, 실시간으로 사용자의 히스토리에 view를 업데이트해야하는 요구 조건이 있었다.
목록 1에 카드를 추가하면, 히스토리 판넬에도 동시에 기록이 추가되어야 한다.
이를 위해 옵저버 패턴을 적용했다.
카드라는 상태를 관리할 TaskState 클래스를 정의해준다.
import {fetchTasksAPI, addTaskAPI, editTaskAPI, deleteTaskAPI} from '../api/taskAPI.js';
export class TaskState {
constructor() {
this.tasks = [];
this._observers = [];
}
subscribe(observer) {
this._observers.push(observer);
}
notify() {
this._observers.forEach(observer => observer(this.tasks));
}
async fetchTasks() {
fetchTasksAPI()
.then((tasks) => {
this.tasks = tasks;
this.notify();
})
}
async addTask(title, description, columnId) {
addTaskAPI(title, description, columnId)
.then(() => this.fetchTasks())
}
...
}
const taskStateInstance = new TaskState();
export default taskStateInstance;
생성자로 this.tasks = [] 배열을 받아 카드 상태를 저장하고, subscriber 를 통해 구독자를 관리한다.
subscribe() 메서드를 통해 상태를 관찰할 구독자를 추가하고, notify()를 통해 구독자에게 알림을 전송한다.
그럼 위와 같은 목록 컴포넌트는 아래처럼 taskstate를 구독한다.
import taskStateInstance from "../states/TaskState.js";
import { fetchColumnsAPI } from '../api/columnAPI.js';
export class TaskifyColumns extends HTMLElement {
constructor() {
super();
this.render();
taskStateInstance.subscribe((tasks) => this.renderColumns(tasks));
taskStateInstance.fetchTasks();
}
async render() {
const columns = await fetchColumnsAPI();
this.columns = columns;
this.renderColumns(taskStateInstance.tasks);
}
renderColumns(tasks) {
this.innerHTML = `
<div class="columns">
${this.columns.map(column => `
<section>
<header class="section-title">
<div class="title-left">
<h2 class="title-name">${column.name}</h2>
<span class="column-count">${tasks.filter(task => task.column_id === column.id).length}</span>
</div>
<div class="title-right">
<button class="add-btn">
<svg class="plus-icon"><use href="/public/icons/icons.svg#plus"></use></svg>
</button>
<button class="delete-section-btn">
<svg class="close-icon"><use href="/public/icons/icons.svg#close"></use></svg>
</button>
</div>
</header>
<ul id="${column.id}" class="column">
<taskify-card-form class="hidden"></taskify-card-form>
${tasks.filter(task => task.column_id === column.id).map(task => `
<taskify-card task-id="${task.id}" task-title="${task.title}" task-description="${task.description}" task-user-id="${task.user_id}"></taskify-card>
`).join('')}
</ul>
</section>
`).join('')}
</div>
`;
this.addCardEvents();
}
}
addCardEvents() {
this.querySelectorAll('.add-btn').forEach(btn =>
btn.addEventListener('click', this.toggleTaskForm.bind(this))
);
}
toggleTaskForm(event) {
const targetColumn = event.target.closest('section').querySelector('ul');
const formComponent = targetColumn.querySelector('taskify-card-form');
formComponent.classList.toggle('hidden');
formComponent.querySelector('#task-form').addEventListener('submit', async (e) => {
e.preventDefault();
const title = formComponent.querySelector('#form-title').value.trim();
const description = formComponent.querySelector('#form-body').value.trim();
const columnId = targetColumn.id;
if (title && description) {
await taskStateInstance.addTask(title, description, columnId);
formComponent.classList.add('hidden');
}
});
...
customElements.define('taskify-columns', TaskifyColumns);
내부적으로 taskInstance의 addTask함수를 통해 카드가 추가된 상태를 다른 옵저버에게 전파하도록 했다.
그럼 이제 히스토리 판넬을 구현해보자. 마찬가지로 카드 상태가 변화할 때 실시간으로 반영되어야한다.
import historyStateInstance from "../states/HistoryState.js";
import taskStateInstance from "../states/TaskState.js";
export class TaskifyHistory extends HTMLElement {
constructor() {
super();
this.render();
this.renderHistories();
taskStateInstance.subscribe(() => {
this.loadHistories();
});
}
render() {
this.innerHTML = `
<aside id="history-panel">
<header class="history-header">
<h2 class="history-title">사용자 활동 기록</h2>
<button id="close-history-btn">
<svg width="16px" height="16px"><use href="/public/icons/icons.svg#close"></use></svg>
<span>닫기</span>
</button>
</header>
<div class="history-body">
<ul id="history-list">
</ul>
</div>
<div class="history-delete-section">
<button id="delete-history-btn">기록 전체 삭제</button>
</div>
</aside>
`;
}
loadHistories() {
historyStateInstance.fetchHistories();
}
renderHistories(histories) {
const historyList = this.querySelector("#history-list");
historyList.innerHTML = '';
histories.sort((a, b) => {
return new Date(b.created_at) - new Date(a.created_at);
});
histories.forEach(history => {
const {from_column_name, to_column_name, task_title, action_type} = history;
const description = buildDescription(from_column_name, to_column_name, task_title, action_type);
const listItem = document.createElement("li");
listItem.classList.add("history-li");
listItem.innerHTML = `
<div class="history-item">
<img class="history-profile-image" src="/public/icons/frog_40x40.png" alt="user-profile">
<div class="history-content">
<p class="history-username">@${history.user_id}</p>
<p class="history-explain">${description}</p>
<time class="history-time">${formatTime(history.created_at)}</time>
</div>
</div>
`;
historyList.appendChild(listItem);
});
}
}
히스토리 판넬에서도 task 상태를 구독한다.
실행 결과를 살펴보면 아래와 같다.
참고자료
'JavaScript' 카테고리의 다른 글
[JS] 구름 LEVEL IDE 입력 받기 (0) | 2024.07.05 |
---|