08 Jul 2019
|
Javascript
Vue
본 강의는 Inflearn의 김정환 개발자 님의 강의(실습 UI 개발로 배워보는 순수 javascript 와 VueJS 개발)를 듣고 배운 내용을 정리한 포스팅 입니다.
최근 검색어 구현 (1), (2)
최근 검색어, 목록이 탭 아래 위치한다
[ ] 목록에서 검색어를 클릭하면 선택된 검색어로 검색 결과 화면으로 이동
1) index.html
- div 태그 추가 (
id="search-history"
)
<div id="search-keyword"></div>
<div id="search-history"></div>
<div id="search-result"></div>
2) HistroyView.js & MainController.js
- KeywordView를 복사하여 구현
- HistoryView와 MainController를 연결
- KeywordView를 연결했던 것과 동일한 방식으로 연결
onClickHistory
함수 구현. search
함수에 keyword를 넘김
selectedTab
의 default값을 “최근 검색어” 로 변경
// HistoryView
import KeywordView from './KeywordView.js'
const tag = '[HistoryView]'
const HistoryView = Object.create(KeywordView)
export default HistoryView
// MainController
import HistoryView from '../views/HistoryView.js'
export default {
init() {
KeywordView.setup(document.querySelector('#search-keyword'))
.on('@click', e => this.onClickKeyword(e.detail.keyword))
HistoryView.setup(document.querySelector('#search-history'))
.on('@click', e => this.onClickHistory(e.detail.keyword))
this.selctedTab = '최근 검색어'
},
onClickKeyword(keyword) {
this.search(keyword)
},
onClickHistory(keyword) {
this.search(keyword)
},
3) MainController.js (2)
- 위의 코드대로 서버를 실행시키면 아래 함수에 따라 debugger로 입력된 breakpoint에서 멈추게된다.
- 분기문 if 조건에서 작성한 코드처럼, 유사한 코드를 ‘최근 검색어’ 에 대해 작성할 수 있음. (
this.fetchSearchHistory()
)
- HistoryModel 내 list 함수를 호출함(promise 객체를 반환함). 결과를 data에 저장하여
HistoryView.render
함수로 넘김. HistoryView에는 render
함수가 따로 존재하지 않지만 KeywordView를 복사하였으므로, 사실 KeywordView의 render
함수라고 봐도 무방하다.
renderView() {
console.log(tag, 'renderView()')
TabView.setActiveTab(this.selctedTab)
if (this.selctedTab === '추천 검색어') {
this.fetchSearchKeyword()
} else {
// debugger
this.fetchSearchHistory()
}
ResultView.hide()
},
fetchSearchHistory() {
HistoryModel.list().then(data => {
HistoryView.render(data)
})
},
최근 검색어 구현 (3)
[ ] 검색일자, x 버튼을 구현
1) 추가 코드
몇 파일에서 추가된 코드를 발견함
// HistoryView.js
HistoryView.messages.NO_KEYWORDS = '검색 이력이 없습니다'
//KeywordView.js
KeywordView.messages = {
NO_KEYWORDS: '추천검색어가 없습니다'
}
2) HistoryView.js
KeywordView.js의 render 함수를 살펴보면 getKeywordHtml
함수를 통해 리스트를 보여주는데 이부분은 HistoryView.js와는 포맷이 조금 다름. => getKeywordHtml 함수를 HistoryView.js에서 오버라이딩하여 재 구현
HistoryView.getKeywordsHtml = function (data) {
return data.reduce((html, item) => {
html += `<li data-keyword="${item.keyword}">
${item.keyword}
<span class="date">${item.date}</span>
<button class="btn-remove"></button>
</li>`
return html
}, '<ul class="list">') + '</ul>'
}
최근 검색어 구현 (4)
[ ] 목록에서 x 버튼을 클릭하면 선택된 검색어가 목록에서 삭제
1) 추가 코드
MainController.js 에서 추가된 코드를 발견함
renderView() {
console.log(tag, 'renderView()')
TabView.setActiveTab(this.selctedTab)
if (this.selctedTab === '추천 검색어') {
this.fetchSearchKeyword()
HistoryView.hide() // 추가된 코드
} else {
this.fetchSearchHistory()
KeywordView.hide() // 추가된 코드
}
ResultView.hide()
},
2) HistoryView.js
- x 버튼을 클릭이벤트와 바인딩 함 =>
bindRemoveBtn
함수 정의
- 해당 함수는 버튼을 모두 찾아서 클릭 이벤트를 달아주는 기능을 함.
- 앞서 작성했던 코드와 동일한 방식으로 코드 작성
e.stopPropagation()
을 통해 클릭의 이벤트 전파를 막음
- x 버튼이 클릭되었다는 것을 MainController에게 알려주기 위한 함수
onRemove
정의
- 어떤 값을 지울건지. 지울려는 키워드를 보냄(키워드는 btn 위에 있는 li 엘레멘트의 data 어트리뷰트에 있음)
HistoryView.bindRemoveBtn = function () {
Array.from(this.el.querySelectorAll('button.btn-remove')).forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation()
this.onRemove(btn.parentElement.dataset.keyword)
})
})
}
HistoryView.onRemove = function (keyword) {
this.emit('@remove', {keyword})
}
3) MainController.js & KeywordView.js
bindRemoveBtn
을 어느시점에서 호출하느냐가 또다른 관건임.
- HistoryView를 render하는 부분을 찾음 =>
fetchSearchHistory
에서 render 함. 이부분에 코드 추가
- render 함수가 호출되고 나면 데이터를 기반으로 DOM이 생성됨. 그 이후 이벤트를 바인딩 할 수 있기 때문에 체이닝을 이용하여
bindRemoveBtn()
을 만듦.
- 체이닝을 할려면
render
함수가 this를 return 하고 있어야함. render은 HistoryView에 있는 것이 아니라 이 HistoryView가 복사해온 KeywordView에 있음. KeywordView의 render 함수에 this
를 return 하도록 하자.
// MainController.js
fetchSearchHistory() {
HistoryModel.list().then(data => {
HistoryView.render(data).bindRemoveBtn()
})
},
// KeywordView.js
KeywordView.render = function (data = []) {
this.el.innerHTML = data.length ? this.getKeywordsHtml(data) : this.messages.NO_KEYWORDS
this.bindClickEvent()
this.show()
return this
}
4) MainController.js
- 추가로
remove
함수를 발산하도록 컨트롤러 초기화 부분에 코드 삽입
onRemoveHistory
함수에 keyword를 넘김 & 함수 구현
- 실제로 데이터를 삭제하는 것은 controller가 아니라 model이 데이터를 관리함.
- HistoryModel 내 미리 구현해 놓은
remove
함수에 삭제할 keyword를 넘겨줌.
- 이후 다시
renderView
를 호출해줌
export default {
init() {
HistoryView.setup(document.querySelector('#search-history'))
.on('@click', e => this.onClickHistory(e.detail.keyword))
.on('@remove', e => this.onRemoveHistory(e.detail.keyword))
최근 검색어 구현 checkout
현재까지 작성한 코드를 기반으로 서버를 실행시키면, TabView의 기본 값은 최근 검색어로 되어있음. 이때 추천 검색어 탭을 클릭하면 설정한 breakpoint에 따라 정지되는데 그 로직은 다음과 같음.
- TabView.setup 함수에 따라
onChangeTab
함수가 실행됨.
onChangeTab
함수에는 debugger
가 설정되어있음.
이 코드를 다시한번 다듬어 보도록 하자.
export default {
// 컨트롤러 초기화 부분
init() {
TabView.setup(document.querySelector('#tabs'))
.on('@change', e => this.onChangeTab(e.detail.tabName))
},
onChangeTab(tabName) {
debugger
},
onChangeTab
함수에는 tabName
이 인자로 들어옴 => this.selectedTab
에 tabName
을 설정해줌
- 이후 다시
renderView
를 호출함
- 마지막으로 탭구현의 2번 명세 (기본적으로 추천 검색어 탭을 선택한다)에 따라, this.selectedTab의 기본 값을
this.selectedTab = '추천 검색어'
으로 다시 변경하도록 하자.
최근 검색어 구현 (5)
[ ] 검색시마다 최근 검색어 목록에 추가된다
MainController.js
- 검색을 하게 되면 MainController의 특정 함수로 수렴하게 되어 있음. 컨트롤러 초기화 부분에 따라, 어떠한 view에서 이벤트가 발생하는 경로를 잘 살펴보면
search
함수로 도달하게 되는 것을 알 수 있음. search 함수에서 검색한 이력을 추가하면 됨.
search(query) {
FormView.setValue(query)
HistoryModel.add(query)
SearchModel.list(query).then(data => {
this.onSearchResult(data)
})
},
08 Jul 2019
|
Javascript
Vue
본 강의는 Inflearn의 김정환 개발자 님의 강의(실습 UI 개발로 배워보는 순수 javascript 와 VueJS 개발)를 듣고 배운 내용을 정리한 포스팅 입니다.
추천 검색어 구현 (1)
번호, 추천 검색어 목록이 탭 아래 위치한다.
1) index.html
- 추천 검색어 목록을 보여줄 코드 구현
- div 엘리먼트 내
search-keyword
id 값 적용
- 실제 데이터는 서버에서 갖고오고, 그 데이터를 기반으로 DOM을 만들어내기 때문에 1개의 div만 만듦.
search-keyword
를 이용하여 KeywordView.js
생성
<div class="container">
<ul id="tabs" class="tabs">
<li>추천 검색어</li>
<li>최근 검색어</li>
</ul>
<div id="search-keyword"></div>
<div id="search-result"></div>
</div>
2) KeywordView.js
- 다른 View와 동일하게 View를 가져와 복사해서 사용 & 디버깅을 위한 태그 생성
setup
함수 & 데이터를 받아서 뿌려줄 render
함수 생성
- 엘레멘트의 innerHTML에 데이터를 넣음. 2가지의 경우 고려
- 데이터가 있는 경우(
data.length
): getKeywordHtml
함수를 통해서 html 문자열을 받아옴
- 그렇지 않은 경우, “추천 검색어가 업습니다” 문자열 출력
this.show()
메서드를 이용하여 실제로 출력
getKeywordHtml
함수 정의. 우선은 간단히 debugger
만 입력
- MainController.js에서 사용 할 수 있도록 모듈을 export 해 줌
import View from './View.js'
const tag = '[KeywordView]'
const KeywordView = Object.create(View)
KeywordView.setup = function(el) {
this.init(el)
}
KeywordView.render = function (data = []) {
this.el.innerHTML = data.length ? this.getKeywordsHtml(data) : '추천 검색어가 없습니다'
this.show()
}
KeywordView.getKeywordsHtml = function (data) {
debugger
}
export default KeywordView
3) MainController.js
- KeywordView.js 를 import 해오고 controller를 초기화하는 코드에 삽입
search-keyword
엘레멘트 (id값) 로 setup 설정
import KeywordView from '../views/KeywordView.js'
export default {
// 컨트롤러 초기화 부분
init() {
// 중략
KeywordView.setup(document.querySelector('#search-keyword'))
- search-keyword에 render하는 코드를 작성. MainController.js 내
renderView
가 그 역할을 담당하고있음.
- controller가 가지고 있는 데이터 중 selectedTab이라는 게 있음. (default값: “추천 검색어”)
- selectedTab 값이 어떤 것이냐에 따라서 “추천 검색어” 또는 “최근 검색어”를 출력하는 로직을 짤 수 있음.
- “추천 검색어” 인 경우 KeywordView.js에
render
함수를 호출함. (현재까지의 코드로는 “추천 검색어가 없습니다” 가 출력됨)
renderView() {
console.log(tag, 'renderView()')
TabView.setActiveTab(this.selctedTab)
ResultView.hide()
if (this.selctedTab=== '추천 검색어') {
KeywordView.render()
} else {
}
},
4) MainController.js (2)
- models/KeywordModel.js 를 통해 추천 검색어를 가져 옴. 기 파일 내
list
함수를 이용해 데이터를 수신
- controller에서 KeywordModel을 가져온 후
renderView
함수 내에서 KeywordModel의 list
를 호출 함.
- 호출한 값은
promise
객체를 반환하므로 .then 함수를 통해 데이터를 얻어 올 수 있음.
- 받아온 데이터를
render
함수에 그대로 넘김
- 이후 서버를 실행시키면,
KeywordView.getKeywordsHtml
함수가 실행되어 debugger
로 지정한 곳에 멈춤. 이는 keywordView.render
함수를 통해 data.length
가 있기 때문에 getKeywordsHtml
이 호출된 것.
- 나머지 우리가 해야할 일은
getKeywordHtml
을 구현하는 것
import KeywordModel from '../models/KeywordModel.js'
renderView() {
console.log(tag, 'renderView()')
TabView.setActiveTab(this.selctedTab)
ResultView.hide()
if (this.selctedTab=== '추천 검색어') {
KeywordModel.list().then(data => {
KeywordView.render(data)
})
} else {
}
},
5) KeywordView.js
- 받은 데이터를 reduce함수를 통해 HTML을 생성
- 초기 값으로
<ul>
입력. style.css를 통해 이미 정의된 css를 적용하기 위해 list 클래스 추가
reduce
함수를 사용하는 매커니즘은 이전 포스팅과 동일
- 추가로, number를 넣기 위해
reduce
의 3번째 인자로 index
를 삽입
KeywordView.getKeywordsHtml = function (data) {
return data.reduce((html, item, index) => {
html += `<li>
<span class="number">${index + 1} </span>
${item.keyword}
</li>`
return html
}, '<ul class="list">') + '</ul>'
6) MainController.js
- MainController.js 내 renderView() 함수에서, 추천검색어를 선택한 경우 모델에서 데이터를 갖고와 KeyworView.render함수에 뿌려주는 코드는 view를 rendering하는 역할이기 때문에 모델에서 데이터를 갖고오는 부분은 별도로 따로 떼 낼 수 도 있음.
- 코드를 떼네어,
fetchSearchKeyword
라는 함수에게 위임
renderView() {
console.log(tag, 'renderView()')
TabView.setActiveTab(this.selctedTab)
if (this.selctedTab=== '추천 검색어') {
this.fetchSearchKeyword()
} else {
}
ResultView.hide()
},
fetchSearchKeyword() {
KeywordModel.list().then(data => {
KeywordView.render(data)
})
},
추천 검색어 구현 (2)
목록에서 검색어를 클릭하면 선택된 검색어로 검색 결과 화면으로 이동
추천 검색어에 있는 리스트를 클릭하면 클릭 이벤트를 수신하도록 하자
1) KeywordView.js (1)
- bind이벤트(
bindClickEvent
)를 setup에서 호출 & 함수 구현
- li 엘레멘트를 찾은 후, 클릭 이벤트를 하나씩 바인딩 하는 코드 작성
- 유사 배열이므로 Array.from을 통해 array로 만든 후, forEach를 통해 array의 요소에 하나씩 접근
- li 엘레멘트에 클릭 이벤트 바인딩한 후 onClickKeyword 함수로 이벤트를 전달 해줌.
KeywordView.bindClickEvent = function() {
Array.from(this.el.querySelectorAll('li')).forEach(li => {
li.addEventListener('click', e => this.onClickKeyword(e))
})
}
2) Keyword.View.js (2)
- onClickKeyword 함수 구현
- 데이터를 클릭했을 때 이벤트는 발생. 이 때 어떤 추천 검색어가 클릭되었는지를 알아야함. 그렇게 하기 위해서는 이벤트(
e
) 에 데이터를 심어서 보내줘야함.
getKeywordHtml
함수 내 reduce
메서드를 통해 html에 쌓아올리는 코드에서 data
변수를 추가
data-keyword
에 실제 출력한 키워드(item.keyword
)를 바인딩 함. 이 데이터를 갖고와 활용
keyword
를 추출 (keyword
는 e.currentTarget.dataset
에 저장되어있음)
- 클릭했을 때 검색 결과 페이지로 넘어가는 역할은 KeywordView의 역할은 아님. 이는 mainController가 다룰 예정이므로, MainController에게 어떤 값이 클릭되었다는 것을 통지만 함(위임) => emit 함수 사용
- 해당 이벤트를 controller가 수신할 수 있도록 setup 함수에서 this를 return 시킴
KeywordView.setup = function(el) {
this.init(el)
this.bindClickEvent()
return this
}
KeywordView.getKeywordsHtml = function (data) {
return data.reduce((html, item, index) => {
html += `<li data-keyword=${item.keyword}>
<span class="number">${index + 1} </span>
${item.keyword}
</li>`
return html
}, '<ul class="list">') + '</ul>'
}
KeywordView.onClickKeyword = function(e) {
const {keyword} = e.currentTarget.dataset
this.emit('@click', {keyword})
}
3) MainController.js
- 해당 이벤트를 MainController에서 수신 할 수 있도록 KeywordView.setup 함수를 체이닝으로 수신
onClickKeyword
함수를 호출하며, 이벤트의 키워드 값을 전달함
- keywordView에서 전달한 값이 제대로 들어오는지 확인하기 위해 breakpoint 설정
export default {
init() {
// 중략
KeywordView.setup(document.querySelector('#search-keyword'))
.on('@click', e => this.onClickKeyword(e.detail.keyword))
},
// 중략
onClickKeyword(keyword) {
debugger
}
}
4) keywordView.js
-
setup에서 bind이벤트를 발생 시키기 않고, DOM이 만들어 진 후에 이벤트를 발생시켜야함.
-
render 함수에서 innerHTML을 추가한 뒤에 bind 이벤트를 발생시키도록 수정
KeywordView.setup = function(el) {
this.init(el)
return this
}
KeywordView.render = function (data = []) {
this.el.innerHTML = data.length ? this.getKeywordsHtml(data) : this.messages.NO_KEYWORDS
this.bindClickEvent()
this.show()
}
5) MainController.js
- OnClickKeyword 함수 구현. 이 함수는 키워드가 클릭을 했을 때 실행 됨.
- 실제 검색을 해야함. 검색하는 기능은 미리 구현해놓은 search 함수 활용
onClickKeyword(keyword) {
this.search(keyword)
},
6) MainController.js
- 여기까지 코드로 서버를 실행시키면 탭과 검색결과가 동시에 뜸
- onClickKeyword 함수가 실행되면서 들어오는 keyword를 search 함수에 넘겨줌
- search 함수는 받은 값을 가지고 searchModel에서 조회를 한 후, 거기서 받은 데이터를 onSearchResult로 넘김.
- onSearchResult에서는 resultView를 그리는 역할을 함. 여기서 그리기 전에, 기존에 있던 TabView와 KeywordView를 숨겨줄 필요가 있음. =>
TabView.hide()
& ` KeywordView.hide()` 삽입!
search(query) {
console.log(tag, 'search()', query)
// search API
SearchModel.list(query).then(data => {
this.onSearchResult(data)
})
},
onSearchResult(data) {
TabView.hide()
KeywordView.hide()
ResultView.render(data)
},
onClickKeyword(keyword) {
this.search(keyword)
},
}
추천 검색어 구현 (3)
검색폼에 선택된 추천 검색어 설정
1) MainController.js
- onClickKeyword 함수는 받은 키워드를 search 함수로 넘겨줌.
search
함수에 FormView를 셋팅하는 로직을 추가 => setValue
함수 호출
search(query) {
FormView.setValue(query)
SearchModel.list(query).then(data => {
this.onSearchResult(data)
})
},
onClickKeyword(keyword) {
this.search(keyword)
},
- FormView.js 내
setValue
함수 구현
inputEl
의` value값에 받아온 value를 저장시킴
FormView.setValue = function(value = '') {
this.inputEl.value = value
}
- 추가로 검색폼에 선택된 추천 검색어를 뜨게했을 때, 삭제가능한 x버튼도 뜨도록 코드를 작성할 수 있음.
- 이부분은 FormView.js 내
showResetBtn
함수를 통해 구현되어있음.
- showResetBtn 값을 true로 주거나, inputEl.value.length을 boolean 값으로 해석하도록 넘겨 줘도 됨.
FormView.setValue = function(value = '') {
this.inputEl.value = value
// this.showResetBtn(true)
this.showResetBtn(this.inputEl.value.length)
4) MainController.js
- 검색폼에 검색어를 삭제했을 경우, TabView가 사라짐. 이 View를 복구하는 코드를 작성할 수 있음.
- 검색 폼에서 x버튼을 누르면 MainController.js 를 통해
@reset
이벤트가 발생함. 그러면 controller에서는 onResetForm()
함수가 실행됨 onResetForm
은 단순히 ResultView를 감쳐주기만 하고 있음.
- 이부분을 수정하여, controller가 view를 그리는 renderView()를 호출하도록 함.
export default {
init() {
FormView.setup(document.querySelector('form'))
.on('@submit', e => this.onSubmit(e.detail.input))
.on('@reset', e=> this.onResetForm())
},
onResetForm(input) {
console.log(tag, 'onResetForm()')
// ResultView.hide()
this.renderView()
},
※ Error 발생 case
이번 챕터를 따라가다가 두 차례 에러가 발생한 이력이 있어 메모를 남김. 두 차례 모두 this.show를 입력하지 않아 발생한 에러였음. 해당 강의는 git을 통해 각 강의별로 branch를 불러와 진행하는데, 자세히 코드를 살펴보면 이전 강의에서 언급하지 않았던 코드가 일부 수정되거나 추가된 경우를 종종 보았다.
- 검색어를 입력하고 엔터를 쳤을 때 결과가 나오지 않은 경우
- ResultView.js 내
render
함수에서 this.show()
삽입
ResultView.render = function(data = []) {
console.log(tag, 'render()', data)
this.el.innerHTML = data.length ? this.getSearchResultsHtml(data) : '검색 결과가 없습니다'
this.show()
}
- 검색어를 삭제한 후, TabView가 뜨지 않고 추천검색어 목록만 뜨는 경우
- TabVivew.js 내
setActiveTab
함수에서 this.show()
삽입
TabView.setActiveTab = function(tabName){
Array.from(this.el.querySelectorAll('li')).forEach(li => {
li.className = li.innerHTML == tabName ? 'active' : ''
})
this.show()
}
08 Jul 2019
|
Javascript
Vue
본 강의는 Inflearn의 김정환 개발자 님의 강의(실습 UI 개발로 배워보는 순수 javascript 와 VueJS 개발)를 듣고 배운 내용을 정리한 포스팅 입니다.
탭 구현 (1)
추천 검색어, 최근 검색어 탭이 검색폼 아래 위치한다.
1) index.html
- 검색 폼과 검색 결과 사이에 탭 구현
- id와 class 값에
tabs
입력. tabs 클래스에 대한 스타일은 style.css에 정의되어있음.
<div class="container">
<ul id="tabs" class="tabs">
<li>추천 검색어</li>
<li>최근 검색어</li>
</ul>
<div id="search-result"></div>
</div>
ul.tabs {
display: flex;
}
.tabs li {
display: inline-block;
width: 50%;
padding: 15px;
text-align: center;
box-sizing: border-box;
border-bottom: 1px solid #ccc;
background-color: #eee;
color: #999;
}
탭 구현 (2)
기본으로 추천 검색어 탭을 선택한다.
style.css에는 tabs 클래스 내 li 태그 내 active 클래스 속성에 대한 스타일이 정의 되어있음. 따라서, active 클래스를 추천 검색어 탭에 적용시키면 됨.
1) TabView.js
- 다른 View들과 마찬가지로, View를 import 하여 객체를 복사. 디버깅을 위해 Tag 정의
TabView
의 setup
이라는 함수를 만들어 엘레멘트를 주입받아 init함수에 전달함.
- 탭에 active 클래스를 추가 하기 위해
setActiveTab
이라는 함수 정의. tabName
이라는 인자를 받음
li
태그를 찾아서 array로 만든 후, forEach 반복문을 돌림.
- 이때, 처음 맞는 li만 반환하는 것이 아니라 전체 데이터를 반환하기 위해,
querySelectorAll()
메서드를 사용해야함.
li
의 className
에다가 active
문자열을 추가해주면 됨.
- 이때
innerHTML
과 TabName
이 같은 경우에만 active 클래스가 추가되도록 삼항 연산자 사용
import View from './View.js'
const Tag = '[TabView]'
const TabView = Object.create(View)
TabView.setup = function(el) {
this.init(el)
}
//active tab을 셋팅하는 함수
TabView.setActiveTab = function(tabName){
Array.from(this.el.querySelectorAll('li')).forEach(li => {
li.className = li.innerHTML == tabName ? 'active' : ''
})
}
// MainController.js 에서 사용할 수 있게 export 시킴.
export default TabView
2) MainController.js
- MainController.js 에서 TabView.js를 호출
- 다름 view와 마찬가지로, init 함수에서 TabView를 setup 함.
- 추천검색어를 선택하도록 만들어야됨. 탭은 추천 검색어 또는 최근 검색어를 선택할 수 있음. 그러므로 controller에서 어떤 탭을 선택했는지 내부적으로 가지고 있으면 좋음.
- controller에
selectedTab
이라는 변수를 정의하고 default로 추천 검색어를 입력함.
- 이후, TabView의
setActiveTab
함수를 호출함. 이때 controller가 가지고있는 selectedTab
을 인자로 넘김
import FormView from '../views/FormView.js'
import ResultView from '../views/ResultView.js'
import TabView from '../views/TabView.js' // TabView 추가
import SearchModel from '../models/SearchModel.js'
const tag = '[MainController]'
export default {
init() {
FormView.setup(document.querySelector('form'))
.on('@submit', e => this.onSubmit(e.detail.input))
.on('@reset', e=> this.onResetForm())
TabView.setup(document.querySelector('#tabs')) // TabView에 대한 setup 설정
ResultView.setup(document.querySelector('#search-result'))
this.selectedTab = '추천 검색어'
TabView.setActiveTab(this.selectedTab)
},
3) MainController.js
- controller에는 이미 3개의 view를 갖고 있음. 이 view들을 한번에 그려줄 함수를 추가로 정의해보자.
renderView
라는 함수 정의. 한번만 renderView
라는 함수를 호출하면 controller가 갖고있는 view들을 다 그릴 수 있도록 renderView
쪽으로 역할을 위임시킴.
export default {
init() {
// 중간의 코드는 중략
this.selctedTab = '추천 검색어'
this.renderView()
},
// 중략
renderView() {
console.log(tag, 'renderView()')
TabView.setActiveTab(this.selctedTab)
ResultView.hide()
},
탭 구현 (3)
각 탭을 클릭하면 탭 아래 내용이 변경된다.
1) TabView.js
- TabView.setup 내에서
bindClick
함수 호출 (클릭 이벤트에 대하여 bind 시키는 기능) & bindClick
함수 정의
- 탭들은 li 엘레멘트로 되어있으므로 li 전체(
querySelectorAll('li')
)를 arrary로 만든 후 반복문을 돌림.
li
에 대해 click event를 listener 하도록 코드 구현 & eventListener는 onClick()
으로 넘겨줌. 이때 li.innerHTML
(즉 , TabName
을 인자로 넘겨줌)
onClick
함수에는 TabName
이 들어옴. setActiveTab
의 인자로 TabName
을 넘김.
- 또한 Tab이 change되었다는것을 Main Controller에게 알려줘야함. TabView는 Tab만 관리하며, 그 아래의 내용은 TabView에서는 신경쓰지 않는 내용임. 따라서 Tab이 변경된 사실만 controller에게 전달하면 됨.
- controller에서 setup 함수를 실행한다음 체이닝을 이용하여 on 이라는 함수를 실행할 예정이므로 TabView.setup에서 this를 return 시켜 줄 것.
TabView.setup = function(el) {
this.init(el)
this.bindClick()
return this
}
TabView.bindClick = function() {
Array.from(this.el.querySelectorAll('li')).forEach(li => {
li.addEventListener('click', e => this.onClick(li.innerHTML))
})
}
TabView.onClick = function (tabName) {
this.setActiveTab(tabName)
this.emit('@change', {tabName})
}
2) MainController.js
@change
이벤트에 대해서 수신하고 있어야함. 이벤트가 들어왔을 때, controller에 onChangeTab이라는 함수를 실행하게 함.(이벤트의 tabName을 전달함 e.detail.tabName
)
- 동작유무를 확인하기 위해 debugger 만 찍어봄. 최종적으로 탭을 누르면 breakpoint에 도달함을 알 수 있음.
export default {
init() {
TabView.setup(document.querySelector('#tabs'))
.on('@change', e => this.onChangeTab(e.detail.tabName))
},
onChangeTab(tabName) {
debugger
}
07 Jul 2019
|
Javascript
Vue
본 강의는 Inflearn의 김정환 개발자 님의 강의(실습 UI 개발로 배워보는 순수 javascript 와 VueJS 개발)를 듣고 배운 내용을 정리한 포스팅 입니다.
검색 결과 구현 (1)
검색 결과가 검색폼 아래 위치한다
1) index.html
- FormView와 다르게 검색결과는 미리 데이터가 미리 정해져있지 않음. 검색 데이터를 서버에 요청하고 서버가 준 검색데이터를 받아서 동적으로 보여줘야함.
- 검색 결과를 위한 ResultView가 mount 될 수 있는 엘레멘트만 넣도록 하면 됨.
- 기본 틀 생성 후, ResultView.js 생성
<div class="container">
<div id="search-result"></div>
</div>
2) ResultView.js
- index.html > search-result 라는 엘레멘트에 무언가를 출력하도록 구현
- FormView와 마찬가지로 View.js를 이용하여 코드 작성
- 디버깅을 위한 태그 정의 및 View 객체를 이용하여 복사
- Setup 메서드 정의
- 엘레멘트를 주입받아 내부 속성으로 엘레멘트를 갖고 있음 (그 역할은 init 메서드가 수행)
import View from '.View.js'
// 디버깅을 위한 태그 정의
const tag = '[ResultView]'
// View 객체를 이용하여 복사
const ResultView = Object.create(View)
// Setup 메서드 정의
// 엘레멘트를 주입받아 내부 속성으로 엘레멘트를 갖고 있음.
// (그 역할은 init 메서드가 수행)
ResultView.setup = function(el) {
this.init(el)
}
3) MainController.js
- ResultView를 MainController에서도 사용할 수 있게 코드 추가
- 컨트롤러를 초기화하는 init 메서드에서 ResultView를 setup
import FormView from '../views/FormView.js'
import ResultView from '../viewsResultView.js'
const tag = '[MainController]'
export default {
// 컨트롤러 초기화 부분
init() {
FormView.setup(document.querySelector('form'))
.on('@submit', e => this.onSubmit(e.detail.input))
.on('@reset', e=> this.onResetForm())
ResultView.setup(document.querySelector('#serach-reesult'))
},
4) ResultView.js
- 실제 검색결과가 그려지기 위해 ResultView에 render 함수 정의
- render 함수는, 서버로부터 받아온 검색결과 데이터를 동적으로 그려주는 역할을 함.
- data는 컬렉션으로 받으며, 없는 경우는 빈배열로 기본값 설정
- 엘레멘트의 innerHTML을 통해 데이터를 받도록 하자, 2가지 경우가 존재(데이터가 있는/없는 경우)
- 배열의 길이가 있는 경우 => 실제 그 데이터로 그리도록 함.(그리는 역할은
getSearchResultsHtml
이라는 함수로 뽑을 예정)
- 데이터가 없는 경우, 문구 삽입
getSearchResultsHtml
는 데이터를 받아 debugger를 걸어 멈추도록 하자
- ResultView를 컨트롤러에서 사용할 수 있도록 모듈을 export
//HTML DOM을 만들어내는 함수
ResultView.render = function(data = []) {
console.log(tag, 'render()', data)
this.el.innerHTML = data.length ? this.getSearchResultsHtml(data) : '검색 결과가 없습니다'
}
ResultView.getSearchResultsHtml = funtion(data) {
debugger
}
export default ResultView;
5) MainController.js
HTML DOM을 만들어내는 render 함수를 MainController가 호출해야됨. 그시점에대해서 생각해보도록 하자.
- Form에서 입력이 발생 했을 때,
@submit
이벤트 발생
@submit
이벤트를 처리하는 것이 onSubmit
함수
onSubmit
함수는 입력데이터를 받아서 검색 요청을 하게 될 것임
- 검색요청하는 메서드
search
를 onsubmit
함수 내에서 호출
Search
함수는 search API를 백엔드로 호출. 그 결과를 받아 onSearchResult
가 처리. 뭔가 데이터를 받아 넘겨주도록 할 것임.
onSearchResult
함수는 서버로 부터 받은 데이터를 ResultView.js > render
메서드로 넘김
import FormView from '../views/FormView.js'
import ResultView from '../viewsResultView.js'
const tag = '[MainController]'
export default {
init() {
FormView.setup(document.querySelector('form'))
.on('@submit', e => this.onSubmit(e.detail.input))
.on('@reset', e=> this.onResetForm())
ResultView.setup(document.querySelector('#serach-reesult'))
},
// step 2
search(query) {
console.log(tag, 'search()', query)
// search API
this.onSearchResult([])
},
onSubmit(input) {
console.log(tag, 'onSubmit()', input)
// Step 1
this.search(input)
},
onResetForm(input) {
console.log(tag, 'onResetForm()')
},
// step 3
onSearchResult(data) {
ResultView.render(data)
},
}
6) 검색 결과 프로세스 Summary
- FormView에서
@submit
이벤트가 발생하였을 때(엔터키가 눌러졌을 때), onSumit 함수가 구동
- 검색 요청을 위해 search 함수 실행
- search 함수는 search API를 통해 데이터를 얻어옴.
- 그 데이터를 받아서 onSearchResult를 실행함
- onSearchResult는 데이터를 받아 ResultView.js 의 render 함수로 넘겨줌
- render 함수는 검색 결과를 그림
7) 실제 API 호출하기
- models/SearchModel.js 내 list 함수를 이용하여 데이터를 갖고 오도록 하자.
- MainController에서 SearchModel import
- 검색하는 search 함수 내에서 SearchModel의 list를 호출함. 이때 검색어를 넘겨줄 것.
- 이 list는 Promise를 반환하기 때문에 then 함수를 쓸 수 있음. then 함수의 검색결과로 data가 넘어오고, 그 data에다가 onSearchResult의 data로 넘겨줌.
import SearchModel from '../models/SearchModel.js'
export default {
/// 중략
search(query) {
console.log(tag, 'search()', query)
SearchModel.list(query).then(data => {
this.onSearchResult(data)
})
},
위의 코드대로 서버를 실행시켜 검색어를 입력한 후 검색을 하면 ResultView.getSearchResultHtml
에서 멈춤. 이말은 ResultView에서 render를 그릴 때, data의 length값이 없었기 때문에, “검색 결과가 없습니다” 라는 결과가 떴지만, 이번에는 data의 length가 있기 때문에, getSearchResultHtml
가 실행되었고, debugger로 인해 break point로 멈춘 것을 확인 할 수 있음.
검색 결과 구현 (2)
검색 결과가 보인다.
실제 검색 결과를 보여주기 위해서 ResultView.js 내 getSearchResultsHtml 함수를 구현해보도록 하자.
1) ResultView.js
- innerHTML의 값으로 들어가므로, HTML 문자열만 return 해주면 됨. 데이터는 collection 이므로, reduce 함수를 이용
- 초기값으로
'<ul>'
을 넘겨주고, 함수가 끝난 후 닫는 ul 태그를 달아줌.
- reduce 콜백함수의 첫번째 인자로 들어가는 누적값에다가 li 태그 및 값을 만들어주면 됨.
- li를 만들어내는
getSearchItemHtml
함수를 만들고 item
데이터를 넘김.
getSearchItemHtml
- li 태그로 이루어진 문자열을 반환함
- 검색 결과에는 상품 이미지와 상품 제목이 들어가므로, 이미지 태그와 p 태그를 이용하여 이미지와 제목을 출력
ResultView.getSearchResultsHtml = function(data) {
return data.reduce((html, item) => {
html+= this.getSearchItemHtml(item)
return html
}, '<ul>') + '</ul>'
}
ResultView.getSearchItemHtml = function (item) {
return `<li>
<img src="${item.image}">
<p>${item.name}</p>
</li>`
}
검색 결과 구현 (3) - 실습
x버튼을 클릭하면 검색폼이 초기화 되고, 검색 결과가 사라진다
1) MainController.js
- 입력한 검색어를 초기화 시켜주는
onResetForm
함수를 활용하여 검색결과를 없앨 수 있음.
- ResultView 함수의 hide 메서드 사용
- ResultView의 hide는 ResultView가 복사한 View 함수 내 정의되어있는 메서드임.
onResetForm(input) {
console.log(tag, 'onResetForm()')
ResultView.hide()
},
2) 나의 코드
2-1) MainController.js
- OnResetFrom 함수에서 ResultView 내 reset이라는 함수를 호출시킴
onResetForm(input) {``
console.log(tag, 'onResetForm()')
ResultView.reset()
},
2-2) ResultView.js
- 엘레멘트의 innerHTML의 값을 초기화시킴
ResultView.reset = function(){
console.log(tag, 'reset()')
this.el.innerHTML = ''
}