Result
View
export default class TabView extends View {
constructor() {
super(qs('#tab-view'));
this.template = new Template();
}
show() {}
bindEvents() {}
}
정적 페이지의 tab-view id를 가진 태그에 작업을 수행한다.
Result와 같이 탭을 보여줄 기능과, 탭을 누르는 행위를 하는 기능이 필요
Controller
export default class Controller {
constructor(store, {searchFormView, searchResultView, tabView}) {
this.store = store;
this.searchFormView = searchFormView;
this.searchResultView = searchResultView;
this.tabView = tabView;
this.subscribeViewEvents();
this.render();
}
}
Controller에는 View와 Model의 제어를 위해 당연히 tabView가 추가 되어야한다.
추가적으로 View에서 구현한 tab을 보여주는 기능과 클릭과 같은 이벤트들을 처리하는 작업을 구현해야한다.
subscribeViewEvents() {
this.tabView
.on('@select', (event) => this.select(event.detail.target));
}
예를 들어, tabView에서 select라는 이벤트가 발생했을 때, chaining 된 on method로 이벤트를 캐치하고 처리한다.
Model
export default class Store {
constructor(storage) {
if (!storage) throw "no storage";
this.storage = storage;
this.searchResult = [];
this.searchKeyword = "";
this.selectedTab = TabType.KEYWORD;
}
}
Store Model 에서는 어떤 키워드를 선택했는지에 대한 정보를 담기 위한 selectedTab 변수가 추가된다.
맨 처음, 렌더링 할 때 default로 추천검색어를 선택한다.
Quest
Q. 각 탭을 클릭하면 탭 아래 내용이 변경되도록 한다.
우선, 강의의 챕터 상 탭 아래 내용은 tab-content로 다른 책임을 가지고 있으므로 잠시 비워둔다.
bindEvents() {
on(this.element, "click", event => this.handleClick(event));
}
handleClick(event) {
const { target } = event;
this.emit("@select", { target });
}
나는 TabView에서 클릭 이벤트가 발생하면 클릭한 주체를 @select 이벤트로 발행했다.
그리고 앞서 Controller에서 작성한, subscribeEvents 메소드에서 tabView로 부터 @select 이벤트가 발생하면 처리한다.
Controller 입장에서, 어떤 태그를 클릭했는지 명확히 알 수 있다. (TabView에서 알려줌)
select(target) {
const tab = target.dataset.tab;
this.store.select(tab);
this.tabView.show(this.store.selectedTab);
}
그리고 target의 tab 정보를 가져와서 store의 selectedTab 변수로 지정한다.
그리고 tabView를 다시 렌더링 한다.
강사님께서는 utility(helper.js)의 delegate를 사용하고 있다.
export function delegate(target, eventName, selector, handler) {
const emitEvent = (event) => {
const potentialElements = qsAll(selector, target);
for (const potentialElement of potentialElements) {
if (potentialElement === event.target) {
return handler.call(event.target, event);
}
}
};
on(target, eventName, emitEvent);
}
1. 상위 tag의 모든 하위 요소를 다 가져온다.
2. 상위 tag의 모든 하위 요소 중 브라우저에서 event가 발생한 요소와 같은 것을 반환한다.
3. 상위 tag의 이벤트로 추가한다.
내 코드의 문제점은 li 태그 외에 다른 태그가 추가되었을 때도 이벤트가 발행된다는 점이다.
위에 작성한 코드에서 bindEvents만 delegate로 수정하면 된다.
handleClick(event) {
const tab = event.target.dataset.tab;
this.emit("@tabchange", { tab });
}
this.tabView
.on('@tabchange', (event) => {this.select(event.detail.tab);});
select(tab) {
this.store.select(tab);
this.tabView.show(this.store.selectedTab);
}
그리고 추가로 명확히 해주었다.
TabView에서 click event가 tab change만 존재하지 않고, 추가적인 event가 발생할 수 있다.
-> event 발행 시 명확한 이벤트 명과 필요한 데이터를 가지고 발행한다.
Tip
class Template {
getTabList() {
return `
<ul class="tabs">
${Object.values(TabType)
.map(tabType => ({ tabType, tabLabel: TabLabel[tabType] }))
.map(this._getTab)
.join("")}
</ul>
`;
}
_getTab({tabType, tabLabel}) {
return `
<li data-tab=${tabType}>
${tabLabel}
</li>
`;
}
}
렌더링을 위한 Template 클래스에서 _getTap method로 map 함수를 돌리는 과정에서 $[tabLabel] 백틱 부분이 계속해서 undefined로 출력됐다.
이유는 js에서 중괄호, {}를 사용하면 파라미터 전달이 아니고 mapping이 되는 것이다. 즉, getTabList() 메소드에서 첫번 째 map 메소드에서 { tabType, tabLabel } 로 바꾸었고, 그 다음 map 메소드에서 _getTab으로 변환하는 중 tabLabel이 tabLable로 오타가 발생해서 매핑이 되지 않는 문제였다.
이 팁은 handleClick 메소드와 같이 작성할 때도 신경써야하는 부분이다.
'JavaScript > Vanila' 카테고리의 다른 글
[Vanila js] 최근 검색 구현하기 (0) | 2024.08.02 |
---|---|
[Vanila js] 키워드 검색 구현하기 (0) | 2024.08.02 |
[Vanila js] 검색 결과 기능 구현 (0) | 2024.07.31 |
[Vanila js] 검색 기능 구현하기 (0) | 2024.07.31 |
[Vanila.js] 사전 코드 이해하기 (0) | 2024.07.31 |