본문 바로가기

Dev.Project/Todolist_project

[TodoMVC] Step 5. To do list Update Item's Content / list 내용 수정하기


Step 5. To do list의 Item 내용 수정하기

다섯번째 단계. 거의 다왔다. 큰 산은 이게 마지막 산이다.

이번에는 추가해줬던 list 내용을 수정해보자.

수정하기 위해서 선택을 하고 입력을 받기 위해 태그를 생성하고

입력을 마치면 다시 생성했던 태그를 지우고 간단해보이지만 의외로 코드가 정말 많이 들어간다.


1단계 . View.js 파일을 작성한다.

1-1. bind에 itemEdit을 추가한다.

view.js code>

1
2
3
4
5
6
7
8
else if(event === 'itemEdit'){
    console.log('View.prototype.bind.itemEdit execute');
    todo.addEventListener('dblclick'function(event){
        var target = event.target;
        if(target.tagName.toLowerCase() === 'label'){
            handler({id: self._getItemId(target)});
        }
    });
cs


list content 내용 수정을 위해서 더블 클릭 이벤트를 추가해준다.

내용이 입력되어 있는 tag는 label 태그이다event 발생지역으로부터, tagName을 추출하여,

label 로부터 들어온 더블클릭이면 이벤트를 발생시킨다.

이벤트 핸들러에게 넘겨줘야할 값은 content 값을 수정할 list의 id값이다.




1-2. bind에 itemEditDone을 추가한다.

view.js code>

1
2
3
4
5
6
7
8
9
//event: itemEditDone
//handler: editItemSave
else if(event === 'itemEditDone'){
    todo.addEventListener('keypress'function(event){
        if(event.keyCode === 13){ //Enter key's kecode = 13
            var target = event.target;
            handler({id: self._itemId(target), title: target.value});
        }
    });
cs


list의 content 내용 수정이 완료되면 완료된 값을 어떠한 방법으로 추가해줘야 한다.

UX 적으로, 수정을 완료했을 때 Enter 키를 통해서 완료를 짓는 것이 자연스럽다.

때문에 Enter key를 입력받아서 수정된 값을 저장하는 함수를 실행시키도록 하자.

handler에게 넘겨줄 값은 수정된 list의 id값과 수정된 title value 이다.




1-3. prototype에 _itemId를 추가한다.

view.js code>

1
2
3
4
5
View.prototype._itemId = function(element){
    var li = element.parentNode;
    console.log('return value = ' + li.dataset.id);
    return parseInt(li.dataset.id, 10);
};
cs


기존에는 _getItemId 메소드를 통해서 list의 id 값을 추출했다.

하지만 하나의 메소드를 더 만들어주는 이유는, 넘겨주는 값이 달라지고,

더이상은 _getItemId 메소드로는 충당할 수 없을 것 같고 하나의 메소드가 너무 비대해지기 때문이다.

그래서 하나의 메소드, _itemId 메소드를 추가해준다.

기본적인 원리는 _getItemId 메소드와 동일하다.




1-4. render에 editItem과 editItemDone을 ViewCommand로 추가해준다.

view.js code>

1
2
3
4
5
6
7
8
9
10
//parameter의 id, title을 넘겨준다.
editItem : function(){
    console.log('View.prototype.render.editItem execute');
    self._editItem(parameter.id, parameter.title);
},
//parameter의 id, title을 넘겨준다.
editItemDone : function(){
    console.log('View.prototype.render.editItemDone execute');
    self._editItemDone(parameter.id, parameter.title);
}
cs


수정을 위한 화면을 보여줘야하고, 수정이 완료된 화면을 보여주기 위해서 두개의 ViewCommand 를 등록해준다

그리고 View의 prototype에 정의할 두 메소드를 실행시킨다.



1-5. prototype에 _editItem 메소드를 추가해준다.

view.js code>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
View.prototype._editItem = function(id, title){
    console.log('View.prototype._editItem execute');
    var listItem = document.querySelector('[data-id="' + id +'"]');
 
    if(listItem){
        listItem.className = listItem.className + 'editing';
 
        var input = document.createElement('input');
        input.className = 'edit';
 
        listItem.appendChild(input);
        input.focus();
        input.value = title;
    }
};
cs


수정할 listItem에 editing이라는 클래스 이름을 추가한다.

style.css 파일을 살펴보면, editing 클래스에 display: none이 추가되어 있다.

createElement를 통해 input 태그를 생성하면, 그 자리에는 edit 클래스 명이 추가된 input이 화면에 보여지고,

기존의 listItem은 사라지게 하는 것이다.

이렇게 하면 보다 자연스러운 content 수정을 유도할 수 있다.

추가할 때는 appendChild 메소드를 사용하였고, 더블클릭을 함과 동시에 커서를 위치시키기 위해 focus 메소드를 사용하였다.

그리고 input의 value로 전달받은 값인 기존의 값 title을 넣어준다.



1-6. prototype에 _editItemDone 메소드를 추가해준다.

view.js code>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
View.prototype._editItemDone = function(id, title){
    console.log('View.prototype._editItemDone execute');
    var listItem = document.querySelector('[data-id="' + id +'"]');
 
    if(listItem){
        var input = document.querySelector('input.edit', listItem);
        listItem.removeChild(input);
        listItem.className = listItem.className.replace('editing''');
 
        var label = document.querySelectorAll('label');
        label.forEach(function(label){
            if(label.parentNode.parentNode === listItem){
                label.textContent = title;
            }
        });
    }
};
cs


수정이 완료되면 list의 클래스 명을 제거해준다.

if 문을 통해서 listItem 이 제대로 select되었는지 한 번 확인해주자.

replace 메소드를 통해서 지정해주었던 클래스 명 editing을 공백으로 교체해주는 방법을 사용한다.

이렇게 하면 editing 클래스명을 지울 수 있다.

그리고 removeChild 메소드를 통해서 더이상 필요없는 input태그를 삭제해준다.

그리고 label 태그에 입력되어 있는 값을 수정된 값으로 교체해준다.

label 태그를 가져와서, 해당 listItem 과 같을 때만 넣어줘야 수정한 값만 해당 listItem 으로 수정된다.





2단계 . controller.js 파일을 작성한다.

2-1. 생성자 함수에 bind를 추가해준다.

controller.js code>

1
2
3
4
5
6
this.view.bind('itemEdit'function(item){
    self.editItem(item.id);
});
this.view.bind('itemEditDone'function(item){
    self.editItemSave(item.id, item.title);
});
cs


이벤트를 걸어주기 위해 Controller 생성자 함수에 bind 함수를 추가해준다.

이번에도 마찬가지로, editItem 과 editItemSave 두 가지를 걸어준다.



2-2. prototype에 editItem 메소드를 추가해준다.

controller.js code>

1
2
3
4
5
6
7
Controller.prototype.editItem = function(id){
    console.log('Controller.prototype.editItem execute');
    var self = this;
    self.model.read(id, function(data){
        self.view.render('editItem', {id: id, title: data[0].title});
    });
};
cs


이벤트가 발생한 data의 값을 읽어오기 위해 model 의 read메소드를 실행시킨다.



2-3. editItemSave 메소드를 추가해준다.

controller.js code>

1
2
3
4
5
6
7
8
9
10
11
12
13
Controller.prototype.editItemSave = function(id, title){
    console.log('Controller.prototype.editItemSave execute');
    var self = this;
    title = title.trim();
 
    if(title.length !== 0){
        self.model.update(id, {title: title}, function(){
            self.view.render('editItemDone', {id: id, title: title});
        });
    } else {
        self.removeItem(id);
    }
};
cs


title 값을 담을 때 이전과 마찬가지로 .trim( ) 메소드를 통해서 공백을 없애준다.

그리고 빈 값인지 확인한 다음, model 의 update 메소드에 해당 id 값과 title 값을 넘겨준다.

그리고 view.render 메소드를 통해 수정이 완료된 값을 화면에 출력한다.




3단계 . model
.js 파일을 작성한다.

3-1 . prototype에 정의해두었던 read 메소드를 수정한다.

model.js code>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Model.prototype.read = function(query, callback){
    var queryType = typeof query;
    callback = callback || function(){};
 
    if(queryType === 'function'){
        callback = query;
        return this.storage.findAll(callback);
    } else if(queryType === 'string' || queryType === 'number'){
        query = parseInt(query, 10);
        this.storage.find({id:query}, callback);
    } else {
        this.storage.find(query, callback);
    }
};
cs


read를 할 때는 어떠한 조건에 맞는 것만 read하는 것이 아니라

findAll을 통해서 모든 값을 읽어와서 따로 query 가 필요하지 않았다.

이번에는 해당 id 값에 해당하는 값을 find 해야하므로,

storage 메소드에 find를 추가해주고 해당 조건을 넘겨줄 query을 인자로 받아서 분기를 나눠줘야 한다.

 



4단계 . storage.js 파일을 작성한다.

4-1 . prototype에 find 메소드를 추가한다 .

storage.js code>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Storage.prototype.find = function(query, callback){
    if(!callback){
        return ;
    }
    var todos = JSON.parse(localStorage[this._dbName]).todos;
 
    callback.call(this, todos.filter(function(todo){
        for(var q in query){
            if(query[q] !== todo[q]){
                return false;
            }
        }
        return true;
    }));
};
cs


Storage의 prototype에 find 메소드를 추가해준다.

하나의 id 값에 해당하는 data를 추출해서, callback 을 호출한다.

이 때 filter 메소드를 활용하여 callback 으로 넘겨진 값의 조건을 true, false를 판단하여

true일 경우에 call을 실행시킨다.

>> javascript array filter 메소드 >>




-The End-