본문 바로가기
개발하기/Google Extension

[EP.03] 비즈니스 용어를 정리하고 해당 페이지의 UI를 구현하기

by lovedeveloping 2024. 12. 19.
반응형

비즈니스 용어 페이지 만들기

안녕하세요 저번 시간에 기본 구조를 만들었으니 이제 본격적으로 구현해보도록 하겠습니다.

알아보니 확장 프로그램을 눌렀을 때 팝업(popup)형태가 있고 사이드 패널(sidePanel) 형태가 있더라고요. 아무래도 서비스를 실제로 제공할 것이기에 사이드 패널을 선택했습니다. 이제 구현해보도록 하겠습니다.

 

데이터 관리 Terms.json 생성

현재는 직접 용어를 관리 할 것이기에 별도의 파일로 관리해줄 것입니다.

{
  "terms": [
    {
      "term": "금일 (Today)",
      "description": "오늘을 뜻하는 비즈니스 용어. 주로 문서나 이메일에서 사용."
    },
    {
      "term": "구두 (Verbal)",
      "description": "말로 전달하는 것을 의미."
    }
  ]
}

해당 파일에 저의한 게 너무 많아 별도의 파일로 올리고 일부만 작성하여 이런 식을 작성 하는 것이라는 점만 알고 있으시면 됩니다.

terms.json
0.03MB

 

저번 시간에 구조를 설명 했음으로 알맞은 위치에 파일을 붙여주시면 됩니다. 별도로 추가 하고 싶거나 저에게 추가해달라고 말씀하시면 추가하겠습니다. 

용어 관리 로직 termManager.js

class TermManager {
  constructor() {
    this.terms = [];            // 전체 용어 목록
    this.currentPage = 1;       // 현재 페이지
    this.itemsPerPage = 10;     // 페이지당 표시할 용어 수
    this.filteredTerms = [];    // 검색 필터링된 용어 목록
  }

  // 용어 데이터 로드
  async loadTerms() {
    try {
      const response = await fetch('data/terms.json');
      const data = await response.json();
      this.terms = data.terms;
      this.filteredTerms = this.terms;
      this.renderTerms();
      this.setupSearch();
    } catch (error) {
      console.error('용어를 불러오는데 실패했습니다:', error);
    }
  }

  // 검색 기능 설정
  setupSearch() {
    const searchInput = document.getElementById('searchInput');
    searchInput.addEventListener('input', (e) => {
      this.searchTerms(e.target.value);
    });
  }

  // 용어 검색
  searchTerms(query) {
    if (!query.trim()) {
      this.filteredTerms = this.terms;
    } else {
      query = query.toLowerCase();
      this.filteredTerms = this.terms.filter(term => 
        term.term.toLowerCase().includes(query)
      );
    }
    this.currentPage = 1;
    this.renderTerms();
  }

  // 용어 목록 렌더링
  renderTerms() {
    const tbody = document.querySelector('.terms-table tbody');
    const startIndex = (this.currentPage - 1) * this.itemsPerPage;
    const endIndex = startIndex + this.itemsPerPage;
    const pageTerms = this.filteredTerms.slice(startIndex, endIndex);

    tbody.innerHTML = pageTerms.map(term => `
      <tr>
        <td>${term.term}</td>
        <td>${term.description}</td>
      </tr>
    `).join('');

    this.renderPagination();
  }

  // 페이지네이션 렌더링
  renderPagination() {
    const totalPages = Math.ceil(this.filteredTerms.length / this.itemsPerPage);
    const paginationDiv = document.querySelector('.pagination');
    
    if (totalPages === 0) {
      paginationDiv.innerHTML = '';
      return;
    }

    paginationDiv.innerHTML = `
      <button id="prevPage" ${this.currentPage === 1 ? 'disabled' : ''}>이전</button>
      <span>${this.currentPage} / ${totalPages}</span>
      <button id="nextPage" ${this.currentPage === totalPages ? 'disabled' : ''}>다음</button>
    `;

    // 페이지네이션 이벤트 리스너
    this.setupPaginationListeners();
  }
}

해당 파일은 좀전에 만든 파일에 대해 내용을 불러와 데이터를 표시할 때 쓰고, 또한 검색, 페이지 (10개 기준)으로 페이지 전환에 관한 기능을 정의해두었습니다. 주석으로 구분하기 쉽게 작성했으니 자세히 알고 싶은 것이 있다면 댓글 달아주세요.

UI 구성 및 스타일 정하기 (sidepanel.html, style.css)

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>비즈코드</title>
  <link href="styles.css" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
  <!-- 상단 네비게이션 버튼 -->
  <div class="button-container">
    <button id="cleanButton">
      <span class="material-icons">list_alt</span>
      용어 정리
    </button>
    <button id="highlightButton">
      <span class="material-icons">highlight</span>
      하이라이트 표시
    </button>
    <button id="noteButton">
      <span class="material-icons">note_add</span>
      빈 상자
    </button>
  </div>

  <!-- 컨텐츠 영역 -->
  <div class="content-area">
    <!-- 용어 정리 영역 -->
    <div id="cleanArea" class="feature-area active">
      <!-- 검색 영역 -->
      <div class="search-container">
        <input type="text" id="searchInput" placeholder="용어 검색...">
      </div>
      <!-- 용어 테이블 -->
      <table class="terms-table">
        <thead>
          <tr>
            <th>용어</th>
            <th>설명</th>
          </tr>
        </thead>
        <tbody>
          <!-- 용어 목록이 동적으로 추가됨 -->
        </tbody>
      </table>
      <!-- 페이지네이션 -->
      <div class="pagination"></div>
    </div>
  </div>

  <script src="js/termManager.js"></script>
  <script src="js/sidepanel.js"></script>
</body>
</html>

먼저 UI의 구조입니다. <tbody/> 부분에 비어 있는 데 <script src /> 로 불러왔기에 문제없습니다. 이렇게 작성한 이유는 HTML 파일에서 직접 불러온 다면 렌더링 시간이 엄청나게 걸릴 것입니다. 

/* 전체 레이아웃 */
body {
  width: 100%;
  height: 100vh;
  margin: 0;
  padding: 15px;
  font-family: 'Noto Sans KR', sans-serif;
  background-color: #f5f5f5;
  box-sizing: border-box;
  overflow-x: hidden;
}

/* 상단 네비게이션 버튼 */
.button-container {
  display: flex;
  gap: 10px;
  justify-content: space-between;
  margin-bottom: 20px;
}

button {
  flex: 1;
  padding: 10px;
  background-color: #4285f4;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.3s;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}

button.gray {
  background-color: #808080;
}

/* 검색 영역 */
.search-container {
  margin-bottom: 16px;
  position: relative;
}

#searchInput {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid #dadce0;
  border-radius: 8px;
  font-size: 14px;
  box-sizing: border-box;
}

#searchInput:focus {
  outline: none;
  border-color: #1a73e8;
  box-shadow: 0 0 0 1px #1a73e8;
}

/* 용어 테이블 */
.terms-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 10px;
  background-color: white;
  border-radius: 5px;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.terms-table th,
.terms-table td {
  padding: 12px 15px;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

.terms-table th {
  background-color: #4285f4;
  color: white;
  font-weight: 500;
}

.terms-table td:first-child {
  font-weight: 500;
  color: #333;
  width: 120px;
}

.terms-table tr:hover {
  background-color: #f8f9fa;
}

/* 페이지네이션 */
.pagination {
  margin-top: 20px;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 15px;
  padding: 10px;
}

.pagination button {
  padding: 8px 20px;
  border: 1px solid #4a90e2;
  background-color: white;
  color: #4a90e2;
  cursor: pointer;
  border-radius: 4px;
  font-size: 14px;
  transition: all 0.2s ease;
}

.pagination button:disabled {
  background-color: #f5f5f5;
  border-color: #ddd;
  color: #999;
  cursor: not-allowed;
}

.pagination span {
  padding: 8px 12px;
  background-color: #f8f9fa;
  border-radius: 4px;
  color: #333;
  font-size: 14px;
  min-width: 80px;
  text-align: center;
  border: 1px solid #ddd;
}

HTML에 대한 CSS 파일입니다. UI를 만들었으니 당연히 CSS도 있어야겠죠?

권한 설정 파일 manifest.json

{
    "manifest_version": 3,
    "name": "비즈코드",
    "version": "1.0",
    "description": "업무 용어에 약한 신입, 그리고 파일에서 내가 모르는 용어들을 확인하는 간편함",
    "permissions": [
      "sidePanel",
      "activeTab",
      "scripting",
      "storage",
      "tabs"
    ],
    "host_permissions": [
      "file://*"
    ],
    "background": {
      "service_worker": "js/background.js"
    },
    "action": {
      "default_title": "비즈코드 열기"
    },
    "side_panel": {
      "default_path": "sidepanel.html"
    },
    "content_scripts": [
      {
        "matches": [
          "<all_urls>",
          "file:///*"
        ],
        "js": ["js/textSelector.js"],
        "css": ["css/textSelector.css"]
      }
    ]
}

권한 설정 파일입니다. 이 부분은 앞서 말씀 드릴 것처럼 권한이라던가 실행할 파일 등등.. 을 정의 해두는 파일입니다. 

 

이제 모든 정보를 입력 했으니 확장 프로그램을 실행시켜보면!

결과 사진
1결과물

완성입니다!! 검색도 문제없이 되고 페이지 전환도 잘 됩니다.

다음 시간에는 하이라이트 표시에 대한 기능을 만들어 보겠습니다. 생각보다 필요한 조건이라던가 다루는 게 까다롭네요..

그래도 열심히 해봐야죠!

저의 전체 코드는 https://github.com/YangMun/BizCode 에 위치 해 있습니다.

감사합니다.

반응형