[Electron] 14.디자인 마크업 적용 및 무한 스크롤 기능 추가 - 레시피 일렉트론 앱

사실 이미 프로젝트는 4월 초에 끝나서 v1.0으로 빌드까지 완료되었다. 근래에 일이 있어 기록이 소홀해졌는데, 더 늦기전에 다시 기억을 더듬으며 기록하기로 한다. 

GitHub

https://github.com/bsorryman/recipe-bag

개요

기획, 디자인 틀만 잡아두었던 HTML 코드를 걷어내고, 회사 동료가 작성해준 HTML, CSS 소스를 적용하였다. 기본적인 틀은 비슷하지만 검색 결과 페이지 내 페이징 방식을 버튼 식에서 무한 스크롤 식으로 변경하였다. 

 

무한 스크롤

  selectRcpByKeyword(keyword, column, offset) {
    if (this.db == null)
      return '';

    if (column == 'all') {
      column = 'tb_fts_recipe'
    }

    let searchQuery;
    let totalQuery;

    if (keyword == null || keyword == '') {
      searchQuery =       
        `
        SELECT id, title, ingredients, instructions
        FROM tb_fts_recipe
        LIMIT ${offset}, 12
        `;      

      totalQuery = 
        `
        SELECT COUNT(*) AS total
        FROM tb_fts_recipe
        `;      
    } else {
      searchQuery =       
        `
        SELECT id, title
        FROM tb_fts_recipe
        WHERE ${column} MATCH '${keyword}'
        ORDER BY bm25(tb_fts_recipe, 10.0, 3.0)
        LIMIT ${offset}, 12
        `;

      totalQuery = 
        `
        SELECT COUNT(*) AS total
        FROM tb_fts_recipe
        WHERE ${column} MATCH '${keyword}'
        `;

    }

    const resultTable = this.db.prepare(searchQuery).all();
    const resultTotal = this.db.prepare(totalQuery).all();

    const result = { resultTable, resultTotal }

    return result;
  }

 

rcp_sqlite_db.js -> selectRcpByKeyword(keyword, column, offset) 

 

검색 결과 목록을 가져오는 SQL 쿼리문 실행 함수이다. 디자인 소스를 반영하면서 '전체 레시피 목록'을 조회하는 기능이 추가되어 재사용하기 위해 함수가 조금 수정되었다. '전체 레시피 목록'을 조회하기 위해, 검색 Keyword가 없이 호출되면 Keyword 없이 레시피를 순서대로 조회한다. 그 외 쿼리문의 수정사항은 없으며 무한 스크롤 기능에서도 그대로 사용된다.

 

// Infinite Scroll (pagination)
window.addEventListener('scroll', ()=>{
  if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
    gPageNum = gPageNum+1;

    if (gAllRecipe == 'y') {
      window.apis.req_searchRcpList('', 'all', gPageNum);      
    } else {
      window.apis.req_searchRcpList(gKeyword, gColumn, gPageNum);      
    }    
  }
});

/**
 * A listener that receives responses to all column search requests.
 */
window.apis.resp_searchRcpList((event, searchResult) => {
  if (searchResult.resultTable.length == 0) {
    if (gPageNum>1 && gPageNomore == false) {
      window.apis.req_showMessage('info', 'Info', 'There are no more results to display.');
      gPageNomore = true;
    } else if (gPageNum==1 && gPageNomore == false) {
      window.apis.req_showMessage('info', 'Info', 'No results were found for your search.');
      $('#keyword').trigger('focus');
  
      $('#list').empty(); //search result hide
    }

  } else if (searchResult != 'error') {
    displaySearchResult(searchResult.resultTable); // Display received search results.
    gTotalRcp = searchResult.resultTotal[0].total;

  } else {
    window.apis.req_showMessage('error', 'Error', 'Error');
  }

  //gLastPage = Math.ceil(gTotalRcp / 10);
  //displayPagination(gPageNum); // After calculating the last page, display 'pagination' immediately.
});

/**
 * A function that displays the received search results in HTML on the page.
 * @param {*} searchResult 
 * @returns 
 */
function displaySearchResult(searchResult) {
  if (gTotalRcp == 0) {
    return;
  }
  let idList = [];
  try {
    searchResult.forEach((value, index) => {
      let child = 
        `
        <li class="item">
        
          <div class="img_wrap"><img src="/assets/img/result_loader64.gif" alt="" id="img_${value.id}" class="item_img"></div>
          <div class="item_tit_warp">
            <h3 class="item_tit">${value.title}</h3>
          </div>
          <div class="btn_wrap">
            <a href="javascript:void(0)" id="download_${value.id}" class="download download_btn">Download</a>
            <a href="javascript:void(0)" id="view_${value.id}" class="poput view_btn">View</a>
          </div>
        </li>         
        `;

      $('#list').append(child);
      idList.push(`${value.id}`);
    });
    /**
     * After displaying the search results, add additional functions 
     * (Because the work to encode the buffer needs to be done in 'Main')
     */
    displayRcpImage(idList);
    setDownloadButton();
    setRcpViewButton();
  } catch (e) {
    /**
     * Catch errors when there is no search result 
     * so that only empty() is executed when there is no search result.
     */
    console.log('displaySearchResult: catch' + e);
    displaySearchResult
  }
}

$('.top_btn').on('click', ()=>{
  $("html, body").animate({ scrollTop: 0 }, "fast"); 
});

 

검색 결과 페이지(search_result)의 renderer.js 

 

기존 버튼식 페이징 소스를 모두 삭제하고 Scroll 이벤트를 추가하였다. 스크롤이 최하단 끝에 닿으면 page 수를 하나 추가시키고 쿼리문 함수에 동일하게 page 파라미터를 전달하여 다음 페이지의 목록들을 조회한다. 이전 page의 목록들은 그대로 두고 바로 밑에 다음 page의 목록들을 이어 붙여서, 마지막 page가 나올 때 까지 연속적으로 이어진 목록들을 볼 수 있다. 

 

추가로 무한 스크롤 방식은 스크롤이 끝도 없이 길어지기 때문에, 최상단으로 이동할 수 있는 탑 버튼이 필요하다.