하나의 파일에 있던 로직을 내 방식대로 아래와 같이 변경
- 각각의 파일은 모듈단위로 생성
- 추가 삭제 수정 등을 관리하는 todoData.js
- DOM의 직접적인 제어를 담당하는 todoView.js
- 사용자 액션에 의한 이벤트를 관리하는 todo.js
// TODO lib
import utilitylib from "./lib/utilitylib.js";
// TODO Data
import todoData from "./todoData.js";
// TODO View
import todoView from "./todoView.js";
const todos = {
handler: {},
// TODO 초기화
init: function () {
this.initHandler();
todoData.init();
todoView.init(this.handler);
},
initHandler: function () {
this.handler = {
handleInputAddKeyup: this.handleInputAddKeyup.bind(this),
handleButtonAddClick: this.handleButtonAddClick.bind(this),
handleCompleteClick: this.handleCompleteClick.bind(this),
handleContentDbclick: this.handleContentDbclick.bind(this),
handleUpdateContentKeyup: this.handleUpdateContentKeyup.bind(this),
handleUpdateContentFocusout: this.handleUpdateContentFocusout.bind(this),
handleRemoveClick: this.handleRemoveClick.bind(this)
}
},
// TODO HANDLER
handleInputAddKeyup: function (e) {
if (e.key === "Enter") {
const { target } = e;
if (utilitylib.emptyValueCheck(target.value, "내용을 입력해 주세요")) return;
const viewData = todoData.add(target.value);
todoView.viewAdd(viewData);
target.value = "";
}
},
handleButtonAddClick: function (e) {
const { target } = e;
const input = target.previousElementSibling;
if (utilitylib.emptyValueCheck(input.value, "내용을 입력해 주세요")) return;
const viewData = todoData.add(input.value);
todoView.viewAdd(viewData);
input.value = "";
},
handleCompleteClick: function (e) {
const { target } = e;
const id = utilitylib.getId(e.target);
const checked = target.checked;
todoData.complete(id, checked);
todoView.viewComplete(id, checked);
},
handleContentDbclick: function (e) {
const id = utilitylib.getId(e.target);
todoView.viewModify(id);
},
handleUpdateContentKeyup: function (e) {
if (e.key === "Enter") {
const { target } = e;
const id = utilitylib.getId(e.target);
const value = target.value;
if (utilitylib.emptyValueCheck(value, "내용을 입력해주세요.")) return;
todoData.update(id, value);
todoView.viewUpdate(target, value);
}
},
handleUpdateContentFocusout: function (e) {
const { target } = e;
const id = utilitylib.getId(e.target);
const value = target.value;
if (utilitylib.emptyValueCheck(value, "내용을 입력해주세요.")) return;
todoData.update(id, value);
todoView.viewUpdate(target, value);
},
handleRemoveClick: function (e) {
const id = utilitylib.getId(e.target);
todoData.remove(id);
todoView.viewRemove(id);
}
}
todos.init();
// TODO lib
import utilitylib from "./lib/utilitylib.js";
// TODO DATA 객체
const todoData = {
_itemList: [],
init: function () {
this.fetchData();
},
fetchData: function () {
// API로 가져온 데이터 셋팅 - 서버구현 후 사용
// this._itemList = this.fetchDataItems();
return this._itemList;
},
getItem: function (id) {
return this._itemList.filter(item => item.id === id);
},
add: function (content) {
const additem = { id: utilitylib.uuid(), complete: false, content };
const completeTodoIdx = this._itemList.findIndex(item => item.complete);
if (completeTodoIdx > -1) {
this._itemList.splice(completeTodoIdx, 0, additem);
}
else {
this._itemList.push(additem);
}
return additem;
},
remove: function (id) {
const removeitem = this.getItem(id);
this._itemList.splice(removeitem, 1);
},
update: function (id, content) {
this._itemList.forEach(item => {
if (item.id === id) {
item.content = content;
return false; // forEach break
}
});
},
complete: function (id, isComplete) {
const updateItemIndex = this._itemList.findIndex(item => item.id === id);
const updateItem = this._itemList.splice(updateItemIndex, 1);
const itemListLength = this._itemList.length;
updateItem[0].complete = isComplete;
if (isComplete) {
this._itemList.splice(itemListLength, 0, updateItem[0]);
}
else {
this._itemList.splice(0, 0, updateItem[0]);
}
},
}
export default todoData;
// TODO lib
import elementlib from "./lib/elementlib.js";
import utilitylib from "./lib/utilitylib.js";
// TODO view 객체
const todoView = {
_viewWrapper: null,
_viewHeader: null,
_viewContents: null,
_viewElementList: null,
handler: null,
// TODO 초기화
init: function (handler) {
this.handler = handler;
this.initViewCreate();
this.render();
},
initViewCreate: function () {
this._viewWrapper = this.initViewWrapperCreate();
this._viewHeader = this.initViewHeaderCreate();
this._viewContents = this.initViewContentsCreate();
},
initViewWrapperCreate: function () {
const viewWrapperData = {
tagName: "div",
attrs: { className: "wrapper-todo" }
};
return elementlib.elementCreate(viewWrapperData);
},
initViewHeaderCreate: function () {
const viewHeaderData = {
tagName: "div",
attrs: { className: "todo-header" },
children: [
{
tagName: "div",
attrs: { className: "todo-title" },
children: [{ tagName: "h2", attrs: { textContent: "TODO TITLE" } }]
},
{
tagName: "div",
attrs: { className: "todo-date-contents" },
children: [
{ tagName: "span", attrs: { className: "todo-year", textContent: "====" } },
{ tagName: "span", attrs: { className: "todo-month", textContent: "==" } },
{ tagName: "span", attrs: { className: "todo-date", textContent: "==" } },
{ tagName: "span", attrs: { className: "todo-day" } }
]
}
]
};
return elementlib.elementCreate(viewHeaderData);
},
initViewContentsCreate: function () {
const viewContentsData = {
tagName: "div",
attrs: { className: "todo-contents" },
children: [
{
tagName: "div",
attrs: { className: "todo-add-contents" },
children: [
{
tagName: "input",
attrs: { className: "add-input", type: "text" },
events: [{ type: "keyup", handler: this.handler.handleInputAddKeyup }]
},
{
tagName: "button",
attrs: { className: "add-button", type: "button", textContent: "+" },
events: [{ type: "click", handler: this.handler.handleButtonAddClick }]
}
]
},
{
tagName: "ul",
attrs: { className: "todo-list" }
},
{
tagName: "div",
attrs: { className: "todo-update-guide", }
}
]
};
return elementlib.elementCreate(viewContentsData);
},
// TODO render
render: function () {
this.renderView();
this.renderViewSetting();
// this.renderViewElementList();
},
renderView: function () {
this._viewWrapper.appendChild(this._viewHeader);
this._viewWrapper.appendChild(this._viewContents);
document.body.appendChild(this._viewWrapper);
},
renderViewSetting: function () {
this.renderViewElementListSetting();
this.renderViewDateElementSetting();
},
renderViewElementListSetting: function () {
this._viewElementList = elementlib.getElement(".todo-list");
},
renderViewDateElementSetting: function () {
const currentDate = utilitylib.getDate();
const year = elementlib.getElement(".todo-year");
const month = elementlib.getElement(".todo-month");
const date = elementlib.getElement(".todo-date");
elementlib.setElementAttribute(year, { textContent: currentDate.year + "년" });
elementlib.setElementAttribute(month, { textContent: currentDate.month + "월" });
elementlib.setElementAttribute(date, { textContent: currentDate.date + "일" });
},
// renderViewElementList: function () {
// elementList?.forEach(element => {
// const viewElement = this.viewCreate(element);
// this._viewElementList.appendChild(viewElement);
// });
// },
// TODO DOM조작
viewCreate: function (viewData) {
const { id, content, complete} = viewData;
const viewElementData = {
tagName: "li",
attrs: { id, className: (complete) ? "disabled" : "" },
children: [
{
tagName: "input",
events: [{ type: "click", handler: this.handler.handleCompleteClick }],
attrs: { className: "todo-checkbox", type: "checkbox", checked: complete }
},
{
tagName: "div",
events: [{ type: "dblclick", handler: this.handler.handleContentDbclick }],
attrs: { className: "todo-content", textContent: content },
children: [
{
tagName: "input",
events: [
{ type: "keyup", handler: this.handler.handleUpdateContentKeyup },
{ type: "focusout", handler: this.handler.handleUpdateContentFocusout }
],
attrs: { type: "text", className: "hide" }
}
]
},
{
tagName: "span",
events: [{ type: "click", handler: this.handler.handleRemoveClick }],
attrs: { className: "todo-remove", textContent: "X" }
}
]
}
return elementlib.elementCreate(viewElementData);
},
viewAdd: function (viewElementData) {
const viewElement = this.viewCreate(viewElementData);
const viewCompleteElement = elementlib.getElement('.disabled');
if (viewCompleteElement) {
this._viewElementList.insertBefore(viewElement, viewCompleteElement);
}
else {
this._viewElementList.appendChild(viewElement);
}
},
viewComplete: function (id, isComplete) {
const viewCompleteElement = elementlib.getElement("#" + id);
if (isComplete) {
elementlib.addClass(viewCompleteElement, "disabled");
this._viewElementList.appendChild(viewCompleteElement);
}
else {
elementlib.removeClass(viewCompleteElement, "disabled");
this._viewElementList.prepend(viewCompleteElement);
}
},
viewModify: function (id) {
const viewElementParent = elementlib.getElement("#" + id);
const viewModeElement = Array.prototype.slice.apply(viewElementParent.childNodes)
.filter(node => node.classList.value === "todo-content")[0];
if (viewModeElement.tagName !== "INPUT" && elementlib.hasClass(viewElementParent, "disabled") === false) {
const modifyModeElement = viewModeElement.querySelector("input"); // TODO 변경
if (elementlib.hasClass(modifyModeElement, "show")) return;
const viewElementValue = elementlib.getText(viewModeElement);
elementlib.setText(viewModeElement, "");
elementlib.setValue(modifyModeElement, viewElementValue);
elementlib.addClass(modifyModeElement, "show");
elementlib.removeClass(modifyModeElement, "hide");
viewModeElement.appendChild(modifyModeElement);
modifyModeElement.focus();
}
},
viewUpdate: function (target, value) {
const viewElementParent = target.parentElement; // elementlib로 빼야하나?
const updateTextNode = elementlib.createText(value);
elementlib.addClass(target, "hide");
elementlib.removeClass(target, "show");
viewElementParent.appendChild(updateTextNode);
},
viewRemove: function (id) {
const viewRemoveElement = elementlib.getElement("#" + id);
this._viewElementList.removeChild(viewRemoveElement);
},
}
export default todoView;
server와 DB작업 후 data와 view의 구조적인 문제로 인해 리팩토링 작업 진행 예정..ㅠㅠ