목록 구현
목록은 총 세가지필드가 존재한다. 할일 / 진행중 / 종료 할일 / 진행중 에 등록된 티켓은 수정 및 삭제가 가능하다. 할일 / 진행중 / 종료 로 서로간에 이동이 가능하다.각 티켓에는 제목, 내용, 제출 또는 수정날짜가 나타나야 한다. 수정버튼을 눌렀을때 수정모달이 띄워져야 한다.
보기 불편해서 임시로 html 에서 간단하게 css 좀 주고
< style >
.itemList > div {
margin : 10px ;
padding : 10px ;
border : 1px solid ;
width : 400px
}
</ style >
각 Item 은 진행 상태 - 할일 /진행중/종료 를 가지며
-> 이는 변경이 가능해야됨 wait / going / end
state.item 에 workState 항목을 넣어서 해당 일의 상황을 표시한다.
wait 을 기본값으로 설정
{
seq : 1 ,
title : '선배!!' ,
contents : '마라탕 사주세요' ,
date : '2024.08.17. 02:43' ,
workState : 'wait' ,
},
// src/components/Item.js
import Component from '../core/Component.js' ;
export default class Item extends Component {
template () {
const { item } = this . props ;
console . log ( 'Item' , this . props );
return `
<div class='title'>제목 : ${ item . title } </div>
<div class='contents'>내용 : ${ item . contents } </div>
<div class='date'>작성시간 : ${ item . date } </div>
<select name="workState" class="workState">
<option value="wait">할일</option>
<option value="going">진행중</option>
<option value="end">종료</option>
</select>
<button>수정</button>
<button>삭제</button>
` ;
}
setEvent () {}
}
select option은 일단 나중에 진행하기로 하고,
기존 내용을 update 할 수 있는 ItemUpdate.js 구현
ItemList 에서 map 진행시 해당 Item 이 '기본' 상태인지, '수정' 상태 인지에 따라서 <Item.js> or <ItemUpdate.js> 를 생성
'수정' 버튼을 클릭
<ItemUpdate.js> 컴포넌트를 렌더링
Input 창을 제공하여 수정 내용을 입력
'수정완료' 버튼을 클릭
수정된 내용을 저장
<Item.js> 컴포넌트를 렌더링
write : false -> 읽기 (기본)
write : true -> 수정
전체 코드
<App.js>
- state.items 에 속성 추가
- updateItem 추가
// src/app.js
// state , method 관리
import Component from './core/Component.js' ;
import ItemList from './components/ItemList.js' ;
import ItemAdd from './components/ItemAdd.js' ;
export default class App extends Component {
// 초기 state
setup () {
this . state = {
items : [
{
seq : 1 ,
title : '선배!!' ,
contents : '마라탕 사주세요' ,
date : '2024.08.17. 02:43' ,
workState : 'wait' ,
write : false ,
},
{
seq : 2 ,
title : '그럼 혹시..' ,
contents : '탕후루도 같이?' ,
date : '2024.08.17. 02:45' ,
workState : 'wait' ,
write : false ,
},
],
};
}
// 자식 컴포넌트가 위치할 dom 요소
template () {
return `
<div data-component="itemAdd"> </div>
<div data-component="itemList" class="itemList"> </div>
` ;
}
// mounted에서 자식 컴포넌트를 마운트
mounted () {
const { addItem , currentTime , inputFocus , updateItem } = this ;
const $itemList = this . $target . querySelector ( '[data-component="itemList"]' );
const $itemAdd = this . $target . querySelector ( '[data-component="itemAdd"]' );
// new 컴포넌트명('DOM'위치 , 전달할 props)
new ItemList ( $itemList , {
items : this . state . items ,
updateItem : updateItem . bind ( this ),
currentTime : currentTime . bind ( this ),
});
new ItemAdd ( $itemAdd , {
items : this . state . items ,
addItem : addItem . bind ( this ),
currentTime : currentTime . bind ( this ),
inputFocus : inputFocus . bind ( this ),
});
}
// 함수 정의
// 추가하기
addItem ( NewContent ) {
const items = [ ... this . state . items ];
const seq = Math . max ( 0 , ... items . map ( v => v . seq )) + 1 ;
const { title , contents , date , workState , write } = NewContent ;
this . setState ({
items : [ ... items , { seq , title , contents , date , workState , write }],
});
}
// focus 하기
inputFocus ( focusSelector ) {
let newFocus = document . querySelector ( focusSelector );
newFocus . focus ();
}
// 수정하기
updateItem ( NewContent ) {
const items = [ ... this . state . items ];
const { seq , title , contents , date , workState , write } = NewContent ;
items . map ( item => {
return item . seq == seq
? {
seq : seq ,
title : title ,
contents : contents ,
date : date ,
workState : workState ,
write : write ,
}
: item ;
});
this . setState ( items );
console . log ( items );
}
currentTime () {
const now = new Date ();
// 사용자 정의 형식 (예: YYYY-MM-DD HH:MM:SS)
const year = now . getFullYear ();
const month = String ( now . getMonth () + 1 ). padStart ( 2 , '0' ); // 월은 0부터 시작하므로 +1
const day = String ( now . getDate ()). padStart ( 2 , '0' );
const hours = String ( now . getHours ()). padStart ( 2 , '0' );
const minutes = String ( now . getMinutes ()). padStart ( 2 , '0' );
const seconds = String ( now . getSeconds ()). padStart ( 2 , '0' );
const formattedDate = ` ${ year } . ${ month } . ${ day } ${ hours } : ${ minutes } ` ;
return formattedDate ;
}
}
<ItemList.js>
- write 값에 따라서 <ItemUpdate> or <Item> 컴포넌트 생성
// src/components/ItemList.js
import Component from '../core/Component.js' ;
import Item from './Item.js' ;
import ItemUpdate from './ItemUpdate.js' ;
export default class ItemList extends Component {
template () {
let ItemDiv = this . props . items . map ( item => {
return `<div data-Item ${ item . seq } ></div>` ;
});
return `
${ ItemDiv . join ( '' ) }
` ;
}
mounted () {
const { items , updateItem , currentTime } = this . props ;
// write 상태에 따라 '읽기' or '수정'
items . map ( item => {
item . write
? new ItemUpdate ( this . $target . querySelector ( `[data-Item ${ item . seq } ]` ), {
item : item ,
updateItem : updateItem ,
currentTime : currentTime ,
})
: new Item ( this . $target . querySelector ( `[data-Item ${ item . seq } ]` ), {
item : item ,
updateItem : updateItem ,
});
});
}
}
<Item.js>
- 수정 / 삭제 버튼 추가
- workState UI 추가
- addEvent 추가 : write 상태 변경
// src/components/Item.js
import Component from '../core/Component.js' ;
export default class Item extends Component {
template () {
const { item } = this . props ;
return `
<div class='title'>제목 : ${ item . title } </div>
<div class='contents'>내용 : ${ item . contents } </div>
<div class='date'>작성시간 : ${ item . date } </div>
<select name="workState" class="workState">
<option value="wait">할일</option>
<option value="going">진행중</option>
<option value="end">종료</option>
</select>
<button class='updateBtn'>수정</button>
<button>삭제</button>
` ;
}
setEvent () {
const { item , updateItem } = this . props ;
this . addEvent ( 'click' , '.updateBtn' , () => {
item . write = true ;
updateItem ( item );
});
}
}
<ItemUpdate.js>
- class 명을 title 로 했더니 반영이 안되서 upTitle 로 변경
- 2개 이상 '수정'을 누르고 아래 컴포넌트를 먼저 '수정완료' 시 맨 위의 컴포넌트 내용으로 저장됨 # 오류1
--> 선택자를 고유하게 만들어서 해결..
--> item 의 상위 div 가 data-Item{item.seq} 이므로 이를 선택자에 활용
// src/components/ItemUpdate.js
import Component from '../core/Component.js' ;
export default class ItemUpdate extends Component {
template () {
const { item } = this . props ;
return `
<div>제목 : <input class='upTitle' value=' ${ item . title } ' placeholder="제목" autocomplete="off" /> </div>
<div>내용 : <input class='upContents' value=' ${ item . contents } ' placeholder="내용" autocomplete="off" /> </div>
<div class='error'></div>
<button class='upBtn'>수정완료</button>
` ;
}
setEvent () {
const { item , updateItem , currentTime } = this . props ;
this . addEvent ( 'click' , '.upBtn' , () => {
let title = document . querySelector (
`[data-Item ${ item . seq } ] .upTitle` ,
). value ;
let contents = document . querySelector (
`[data-Item ${ item . seq } ] .upContents` ,
). value ;
let error = document . querySelector ( `[data-Item ${ item . seq } ] .error` );
if ( title == '' || contents == '' ) {
error . innerHTML = '입력 값을 확인해 주세요' ;
} else {
item . title = title ;
item . contents = contents ;
item . write = false ;
item . date = currentTime ();
updateItem ( item );
}
});
}
}
// src/components/ItemAdd.js
// 입력UI 와 입력기능
import Component from '../core/Component.js' ;
export default class ItemAdd extends Component {
template () {
return `
<input class='title' name='title' placeholder="제목" autocomplete="off" />
<input class='contents' name='content' placeholder="내용" autocomplete="off" />
<button class='addBtn' type='button'>제출</button>
<div class='error'></div>
` ;
}
setEvent () {
const { addItem , currentTime , inputFocus } = this . props ;
let inputCheck = () => {
let title = document . querySelector ( '.title' ). value ;
let contents = document . querySelector ( '.contents' ). value ;
let error = document . querySelector ( '.error' );
let date = currentTime ();
if ( title == '' || contents == '' ) {
// error 처리
error . innerHTML = '입력 값을 확인해 주세요' ;
} else {
// state 추가
addItem ({
title : title ,
contents : contents ,
date : date ,
workState : 'wait' ,
write : false ,
});
// 추가 후 focus
inputFocus ( '.title' );
}
};
// 초기 focus
inputFocus ( '.title' );
// 클릭 이벤트
this . addEvent ( 'click' , '.addBtn' , () => {
inputCheck ();
});
// enter키 이벤트
this . addEvent ( 'keyup' , '.title, .contents' , e => {
if ( e . key === 'Enter' ) {
inputCheck ();
}
});
}
}
<! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Document </ title >
< style >
.itemList > div {
margin : 10px ;
padding : 10px ;
border : 1px solid ;
width : 400px
}
</ style >
</ head >
< body >
< div id = "app" ></ div >
<!-- build 전에 확인 -->
< script src = "./js/main.js" type = "module" ></ script >
</ body >
</ html >
오류1
- 2개 이상 '수정'을 누르고 아래 컴포넌트를 먼저 '수정완료' 시 맨 위의 컴포넌트 내용으로 저장됨
--> 선택자를 고유하게 만들어서 해결..
목록 구현
목록은 총 세가지필드가 존재한다. 할일 / 진행중 / 종료 할일 / 진행중 에 등록된 티켓은 수정 및 삭제가 가능하다. 할일 / 진행중 / 종료 로 서로간에 이동이 가능하다.각 티켓에는 제목, 내용, 제출 또는 수정날짜가 나타나야 한다. 수정버튼을 눌렀을때 수정모달이 띄워져야 한다.
만들고 보니까... ' 수정버튼을 눌렀을때 수정모달이 띄워져야 한다. ' 는 내용을 이제 봤는데..
선택구현
'수정을 클릭하면 모달이 아닌 현재 티켓내에서 입력폼이 나타나고 수정 가능하다.'
는 기능을 만든셈 치고
모달 기능은 후에 '수정모달' 버튼으로 다시 구현해보겠다.
다음으로는 할일/진행중/종료 기능을 완성해야겠다.