<html>
<head>
<title>2022 FE 데브매칭</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<main class="App">
<ul class="SelectedLanguage"></ul>
</main>
<script src="/index.js" type="module"></script>
</body>
</html>
import App from "./App.js";
new App({ target: document.querySelector(".App") });
// 전체적인 구조
// App 다른 기능들을 통제하는 중앙 제어 컴포넌트 느낌
// 화면에 보여지거나 처리되는 대부분의 정보를 state로 가지고 있고,
// 다른 컴포넌트들에서 사용되어지도록 전달해주기
// SearchInput
// 실질적으로 입력을 받는 부분
// App의 setState를 사용하여 입력된 정보를 App 컴포넌트에 전달해주기
// SelectedLanguage
// 엔터키를 통해 선택된 언어 배열을 전달받아 보여주는 컴포넌트
// 딱히 무언가 변경하지 않고 보여주기만 하기
// Suggestion
// api를 사용하여 넘겨받은 정보를 화면에 보여주는 컴포넌트
// 방향키를 사용하여 선택하거나 마우스 클릭을 통해 선택되어진 언어를 App에 전달해주기
// api
// 입력된 값을 통해 변경되는 API주소에 요청을 넣어 반환값을 돌려주는 컴포넌트
// 얘를 가지고 Input에서 무언가 변화가 있을 때 API요청을 넣고, 반환되는 값을 App에 전달해주기
import SearchInput from "./SearchInput.js";
import SelectedLanguages from "./SelectedLanguages.js";
import Suggestion from "./Suggestion.js";
export default function App({ target }) {
this.state = {
searchResult: [],
selectedLanguages: [],
currentInput: "",
};
this.setState = (nextState) => {
this.state = {
...this.state,
...nextState,
};
suggestion.setState({
searchResult: this.state.searchResult,
selectedIndex: 0,
currentInput: this.state.currentInput,
});
};
const searchInput = new SearchInput({
target: target,
onChange: (fetchedLanguages) => {
this.setState({
searchResult: fetchedLanguages,
});
},
setCurrentInput: (input) => {
this.setState({
...this.state,
currentInput: input,
});
},
});
const suggestion = new Suggestion({
target: target,
selectLanguage: (language) => {
if (!this.state.selectedLanguages.includes(language)) {
let selectedArray = [...this.state.selectedLanguages, language];
if (selectedArray.length > 5) {
selectedArray = selectedArray.splice(1, 5);
}
this.state = {
...this.state,
selectedLanguages: selectedArray,
};
selectedLanguages.setState({
selectedLanguages: this.state.selectedLanguages,
});
console.log(this.state.selectedLanguages);
}
},
});
const selectedLanguages = new SelectedLanguages({
target: document.querySelector(".SelectedLanguage"),
deleteLanguage: (language) => {
this.state = {
...this.state,
selectedLanguages: this.state.selectedLanguages.filter(
(l) => l !== language
),
};
selectedLanguages.setState({
selectedLanguages: this.state.selectedLanguages,
});
},
});
}
const API_END_POINT = `https://wr4a6p937i.execute-api.ap-northeast-2.amazonaws.com/dev`;
const request = async (url) => {
const response = await fetch(url);
if (response.ok) {
return response.json();
}
throw new Error("요청에 실패했습니다.");
};
export const Fetch = async (input) =>
request(`${API_END_POINT}/languages?keyword=${input}`).catch((Error) =>
console.log(Error.message)
);
import { Fetch } from "./api.js";
export default function SearchInput({ target, onChange, setCurrentInput }) {
this.element = document.createElement("form");
this.element.className = "SearchInput";
this.element.innerHTML = `
<input class="SearchInput__input" placeholder="프로그램 언어를 입력하세요."/>
`;
this.element.addEventListener("submit", (e) => {
e.preventDefault();
});
target.appendChild(this.element);
this.element.addEventListener("keyup", async (e) => {
const inActivateKey = ["ArrowUp", "ArrowDown", "Enter"];
const keyword = e.target.value;
if (!inActivateKey.includes(e.key)) {
setCurrentInput(keyword);
if (keyword !== "")
await Fetch(keyword).then((data) => {
onChange(data);
});
else onChange([]);
}
});
}
export default function Suggestion({ target, selectLanguage }) {
this.element = document.createElement("ul");
this.element.className = "Suggestion";
this.element.style.display = "none";
target.appendChild(this.element);
this.state = {
searchResult: [],
selectedIndex: 0,
currentInput: "",
};
this.setState = (nextState) => {
this.state = {
...this.state,
...nextState,
};
this.render();
};
this.render = () => {
const searchResult = this.state.searchResult;
const selectedIndex = this.state.selectedIndex;
if (searchResult.length > 0) {
this.element.innerHTML = `
${searchResult
.map((item, index) => {
const regexp = new RegExp(this.state.currentInput, "ig");
const language = item.replace(
regexp,
`<span class="Suggestion__item--matched">$&</span>`
);
return `<li class="${
selectedIndex === index ? "Suggestion__item--selected" : ""
}">${language}</li>`;
})
.join("")}
`;
this.element.style.display = "block";
} else {
this.element.style.display = "none";
}
};
window.addEventListener("keyup", (e) => {
const activateKey = ["ArrowUp", "ArrowDown", "Enter"];
const lastIndex = this.state.searchResult.length - 1;
if (activateKey.includes(e.key)) {
switch (e.key) {
case "ArrowUp":
this.setState({
selectedIndex:
this.state.selectedIndex === 0
? lastIndex
: this.state.selectedIndex - 1,
});
break;
case "ArrowDown":
this.setState({
selectedIndex:
this.state.selectedIndex === lastIndex
? 0
: this.state.selectedIndex + 1,
});
break;
case "Enter":
selectLanguage(this.state.searchResult[this.state.selectedIndex]);
break;
}
}
});
this.element.addEventListener("click", (e) => {
selectLanguage(
e.target.innerHTML
.replace(/\<span class=\"Suggestion\_\_item\-\-matched\"\>/g, "")
.replace(/\<\/span\>/g, "")
);
});
this.render();
}
export default function SelectedLanguages({ target, deleteLanguage }) {
this.state = {
selectedLanguages: [],
};
this.setState = (nextState) => {
this.state = {
...this.state,
...nextState,
};
this.render();
};
this.render = () => {
target.innerHTML = `
${this.state.selectedLanguages
.map(
(item) =>
`<li style="margin-right: 8px; cursor: pointer;">${item}</li>`
)
.join("")}
`;
};
target.addEventListener("click", (e) => {
console.log(e.target.innerHTML);
deleteLanguage(e.target.innerHTML);
});
this.render();
}
css파일은 따로 올리지 않았습니다. 프로그래머스 문제를 직접 풀어보며 확인해보시길 바랍니다!
추가적으로 선택된 언어의 삭제 기능도 한번 구현해보았으니 혹시나 보시는 분이 있다면 분석해보시는 것도 좋을 것 같습니다!!