[Web] MVC Pattern Practice
MVC Pattern
개념만 배우고 한번도 적용해본 적 없는 MVC 페턴. 직접 코드에 적용시켜 보고자 글을 작성합니다.
말이 됩니까..
4학년이 이제서야 MVC 페턴 실습을 하고 있다는 것이..!
MVC가 뭘까
우선 MVC란 Model, View, Controller의 세 영역이다.
즉, 각 영역의 코드 결합도를 최소화 시키는 것을 목적으로 하는 개발 페턴
- 사용자는 application과 상호작용
- controller의 event handler 작동
- Model에서 데이터 전송 → controller → 결과 : View
- View는 결과 rendering
→ 결론적으로 페턴의 주요 목적은 모델(기능)과 뷰(렌더링)의 코드 결합도를 최소화 시키는 것
역할 정의
Model : 어플리케이션의 비지니스 로직, 사용되는 데이터를 다루는 영역
View : 최종 사용자에게 보여줄 화면 렌더링 담당
Controller: Model과 View 영역간의 코드 결합도를 줄여주는 영역 (중요!!)
실습 코드
- 이번 우아한 테크코스 프리코스를 진행하며 조금이나마 익숙해질 수 있었던 Class형으로 MVC를 구현해봤다.
- Model.js
class Item {
constructor(content) {
this.content = content;
this.finished = false;
}
}
export default class Model {
constructor() {
this.list = [];
}
addItem(content) {
this.item = new Item(content);
this.list.push(this.item);
}
removeItem(itemIndex) {
this.list.splice(itemIndex, 1);
}
checkItem(itemIndex) {
const currentItem = this.list[itemIndex];
currentItem.finished = !currentItem.finished;
}
}
- View.js
export default class View {
constructor(list) {
this.toDoList = document.querySelector(".to-do-list");
this.finishedList = document.querySelector(".finished-list");
this.list = list;
}
showList(list) {
this.finishedList.innerHTML = "";
this.toDoList.innerHTML = "";
list.forEach((item, i) => {
if (!item.finished) {
console.log(item);
const toDoItemHTML =
'<li class="to-do-list__item" id="item-' +
i +
'">' +
'<div class="item__content">' +
item.content +
"</div>" +
'<div class="item__action">' +
'<i class="fa fa-trash"></i>' +
'<input type="checkbox">' +
"</div>" +
"</li>";
this.toDoList.insertAdjacentHTML("afterbegin", toDoItemHTML);
console.log(item.content);
return;
}
const finishedItemHTML =
'<li class="to-do-list__item" id="item-' +
i +
'">' +
'<div class="item__content">' +
item.content +
"</div>" +
'<div class="item__action">' +
'<i class="fa fa-trash"></i>' +
'<input type="checkbox" checked>' +
"</div>" +
"</li>";
this.finishedList.insertAdjacentHTML("afterbegin", finishedItemHTML);
});
}
}
- Controller.js
import Model from "./Model.js";
import View from "./View.js";
export default class Controller {
constructor() {
this.model = new Model();
this.view = new View();
this.form = document.forms["list-form"];
this.addInput = this.form["add-item__input"];
this.searchInput = this.form["search-item__input"];
this.section = document.querySelector("section");
this.setfunction();
}
setfunction() {
this.addItem();
this.searchItem();
this.removeItem();
this.checkItem();
}
addItem() {
this.form.addEventListener("submit", (e) => {
e.preventDefault();
const addVal = this.addInput.value;
console.log(addVal);
this.model.addItem(addVal);
console.log(this.model.list);
this.view.showList(this.model.list);
this.form.reset();
});
}
searchItem() {
this.searchInput.addEventListener("input", (e) => {
e.preventDefault();
const searchVal = this.searchInput.value;
const filterList = this.model.list.filter((item) => {
return item.content.indexOf(searchVal) > -1;
});
this.view.showList(filterList);
});
}
removeItem() {
this.section.addEventListener("click", (e) => {
if (e.target.tagName !== "I") return;
console.log("remove");
const itemId = e.target.parentNode.parentNode.id;
const itemIndex = itemId.split("-")[1];
this.model.removeItem(itemIndex);
this.view.showList(this.model.list);
});
}
checkItem() {
this.section.addEventListener("change", (e) => {
console.log("check");
if (e.target.tagName !== "INPUT") return;
const itemId = e.target.parentNode.parentNode.id;
const itemIndex = itemId.split("-")[1];
this.model.checkItem(itemIndex);
this.view.showList(this.model.list);
});
}
}
- index.js
import Controller from "./Controller.js";
import Model from "./Model.js";
import View from "./View.js";
new Model();
new View();
new Controller();
- index.html (기술 블로그 MVC 실습에서 가져왔다.)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>mvc pattern practice</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="list-container">
<header>
<form id="list-form">
<div class="list-form__add-item">
<input type="text" id="add-item__input" required autofocus />
<input type="submit" value="✚" />
</div>
<div class="list-form__search-item">
<input type="search" id="search-item__input" />
<label for="search-item__input">
<i class="fa fa-search"></i>
</label>
</div>
</form>
</header>
<section>
<ul class="to-do-list"></ul>
<hr />
<ul class="finished-list"></ul>
</section>
<div class="text-center">
<a href="https://github.com/EastSun5566" target="_blank">
<i class="fa fa-github" aria-hidden="true"></i>
</a>
</div>
</div>
<script type="module" src="src/index.js"></script>
</body>
</html>
- style.css
→ 사실 style은 크게 중요하지 않지만, index.html을 가져올 때 같이 가져왔다.
@import url(//fonts.googleapis.com/earlyaccess/notosanstc.css);
* {
/* border: 1px solid #000; */
position: relative;
box-sizing: border-box;
}
:root {
--color-primary: #d3cce3;
--color-secondary: chocolate;
--color-font-dark: darkred;
--color-font-light: #eee;
}
body {
margin: 0;
min-height: 100vh;
background: radial-gradient(circle, #e9e4f0, var(--color-primary));
color: var(--color-font-dark);
letter-spacing: 2px;
font-family: "Noto Sans TC", sans-serif;
display: flex;
justify-content: center;
align-items: center;
}
/* list container */
.list-container {
width: 60%;
padding: 16px 0;
}
@media (max-width: 500px) {
.list-container {
width: 100%;
}
}
/* form */
#list-form {
display: flex;
justify-content: space-around;
}
.list-form__add-item,
.list-form__search-item {
width: 45%;
}
input {
background-color: transparent;
border: 2px solid var(--color-secondary);
border-radius: 100px;
padding: 16px;
text-indent: 8px;
color: var(--color-font-dark);
letter-spacing: 2px;
transition: 0.5s;
}
input:focus {
outline: none;
}
/* add input */
input[type="text"],
input[type="search"] {
width: 100%;
}
#add-item__input:focus,
#search-item__input:focus {
background-color: var(--color-secondary);
color: var(--color-font-light);
}
input[type="text"] {
padding-right: 40px;
}
input[type="submit"] {
border: none;
font-size: 16px;
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
transition: 0.5s;
cursor: pointer;
}
/* serch input */
input[type="search"] {
text-indent: 24px;
}
label[for="search-item__input"] {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
transition: 0.5s;
cursor: pointer;
}
#add-item__input:focus + input[type="submit"],
#search-item__input:focus + label[for="search-item__input"] {
color: var(--color-font-light);
}
/* list */
ul {
list-style: none;
padding: 0;
}
/* to do list */
.to-do-list:empty::before {
content: "Nothing to do...";
display: block;
text-align: center;
font-size: 12px;
font-weight: lighter;
}
/* finished list */
.finished-list:empty::before {
content: "Nothing finished...";
display: block;
text-align: center;
font-size: 12px;
font-weight: lighter;
}
li {
border: 1.5px solid transparent;
border-radius: 100px;
margin: 8px;
padding: 8px;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
transition: 0.5s;
/* animation: fadeIn 0.5s both; */
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
li:hover {
/* border-color: var(--color-secondary); */
}
.item__content {
max-width: 80%;
overflow: hidden;
text-overflow: ellipsis;
}
/* item action */
.item__action {
width: 20%;
display: flex;
justify-content: space-between;
align-items: center;
}
/* check input */
input[type="checkbox"] {
appearance: none;
padding: 8px;
transition: 0.3s;
cursor: pointer;
}
input[type="checkbox"]:checked,
input[type="checkbox"]:hover {
background-color: var(--color-secondary);
}
input[type="checkbox"]:checked::after {
content: "✔";
position: absolute;
top: 50%;
left: 35%;
transform: translate(-50%, -50%);
color: #eee;
font-weight: bold;
}
.fa-trash {
display: block;
cursor: pointer;
background-color: aqua;
transition: 0.3s 0.1s;
}
.fa-trash::after {
content: "";
position: absolute;
right: -10px;
width: 3px;
height: 100%;
background-color: var(--color-secondary);
z-index: -1;
transition: 0.3s;
}
.fa-trash:hover {
color: var(--color-font-light);
}
.fa-trash:hover::after {
width: 200%;
}
/* hr */
hr {
width: 80%;
border: 1px solid var(--color-secondary);
opacity: 0.5;
}
.text-center {
text-align: center;
}
아직 갈길은 멀다.. 왜냐 MVC 패턴에도 많은 문제가 있다.
MVVM, MVP 등 여러가지 페턴에 대해 공부할 것
조금 더 효율적인 디렉토리 파일 구성을 위해 더 열심히 공부할 것.
댓글남기기