Problem

고도몰에서는 검색 시 자동 완성 기능을 제공하지 상품이 많은 경우 노출에 한계가 있어서 불편함이 있다. 현재는 최근 검색한 기록만 제공하고 있어서 설정이나 부가 서비스로는 설정이 불가능하다. 그래서 ajax를 이용해 상품 결과를 확인할 수 있는 기능을 만들어 보고자 한다. 일부 기능은 고도몰과 관련된 설정이지만 수정한다면 일반적인 쇼핑몰에도 적용할 수 있을 것이다. 아래 구글 검색과 비슷한 화면을 구현하고자 한다.

Solution

고도몰에서 직접 php 파일을 만든다면 $_GET 을 바로 사용할 수 없었다. 여기 문서에 보면 $_GETunset 되었다고 나와있다. 따라서 아래와 같이 별도 제공하는 클래스를 사용해야 한다. 코드 내용 중 해당 쇼핑몰을 위한 구현도 많아서 전체 코드는 추가하지 못했다.

데이터베이스 접속 정보는 /config/database.php 파일에서 확인할 수 있었다.

<?php
return [
    'host' => 'host.godomall.com',
    'username' => 'username',
    'password' => 'password',
    'database' => 'database',
];

/extensions/search.php 에 아래와 같은 파일을 만들고 쿼리스트링 q로 원하는 결과의 응답이 오면 해상 상품을 json형태로 반환하도록 구현하였다.

<?php
use Framework\Http\Request;
$_GET = \Request::get();

$q = trim($_GET->get('q'));
$config = @require('/[PATH_HERE]/config/database.php');
$conn = new mysqli($config['host'], $config['username'], $config['password'], $config['database']);
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

$sql = "SELECT * FROM es_goods where goodsNm like '%" . $q ."%' limit 10";
$result = $conn->query($sql);
$data = [];

if ($result->num_rows > 0) {
    while ($row = $result->fetch_assoc()) {
        array_push($data, ['id' => $row["goodsNo"], 'name' =>  $row["goodsNm"]]);
    }
}

$conn->close();

header('Content-type: application/json');
echo json_encode($data);

이제 페이지를 호출해보자. querya를 입력했고 a가 포함된 결과가 json 포맷으로 응답되는 것을 확인할 수 있다. /extensions/search.php?q=a

[{"id":"1000000138","name":"iHope Zipper Card Wallet Grey"},{"id":"1000000414","name":"Paper Bug Paper Book"},{"id":"1000000892","name":"Double Lovers x Viva Studio Whiskey[Wiss-Key] Black"},{"id":"1000000903","name":"Bon Tote205 Mild Black"},{"id":"1000000954","name":"Oldies Candle Palmarosa & Basil 330ml"},{"id":"1000001017","name":"Neck Point Pola T Black"},{"id":"1000001030","name":"Karmuel Maple Wood Genuine Leather"},{"id":"1000001043","name":"Nick Night L Boots Black"},{"id":"1000001074","name":"Holiday Moon Earrings Sliver"},{"id":"1000001081","name":"Pearl & Crystal Necklace"}]

이제 검색창에 이벤트를 추가해보자. 특이한 것은 아래와 같이 두가지 종류의 이벤트를 구현하였다. keydown이나 keyup 이벤트 대신 input을 이용한 것은 ios safari에서 타이핑 후 바로 입력을 확인하기 위해서 별도로 분리하였다.

document.getElementById("search_form").addEventListener("input", function(e) {})

이 부분에 아래와 같이 검색 결과를 이용해 내용을 표시하는 부분을 구현하였다. 추가로 빠르게 입력 시 중복해서 요청하기 않도록 하였다.

clearInterval(searchHandler);
searchHandler = setTimeout(function() {
    $.get('/extensions/search.php', {
        'q': q,
    }, function(data, status) {
        if (status == 'success' && data) {
            recentBox.addClass('search');
            container.empty();

            if (data.length > 0) {
                // recentBox.removeClass('empty');
                searchCont.show();

                var ul = $('<ul></ul>').addClass('js_recent_list');
                for (var i = 0; i < data.length; ++i) {
                    ul.append('<li><a href="../goods/goods_search.php?keyword=' + data[i] + '">' + data[i] + '</a></li>');
                }

                container.append(ul);
            } else {
                // recentBox.addClass('empty');
                searchCont.hide();
            }

        } else {
            console.log('request fail. ajax status (' + status + ')');
        }
    });
}, 100);
$('input#search_form').on('keydown', function(e) {})

이 부분에는 검색 후 화살표(위, 아래)로 검색 결과를 선택할 수 있도록 기능을 구현하였다.

switch (e.keyCode) {
  case 13:
  case 37:
  case 39:
      break;
  case 38:
      var selected = container.find('li.selected');
      container.find('li.selected').removeClass('selected');

      if (selected.length) {
          selected = selected.prev();
          selected.addClass('selected');
      } else {
          container.find('li:last').addClass('selected');
      }

      var text = container.find('li.selected').text();
      if (text) { that.val(text); }
      break;

  case 40:
      var selected = container.find('li.selected');
      container.find('li.selected').removeClass('selected');

      if (selected.length) {
          selected = selected.next();
          selected.addClass('selected');
      } else {
          container.find('li:first').addClass('selected');
      }

      var text = container.find('li.selected').text();
      if (text) { that.val(text); }
      break;

  default:
      break;
}

적용 후 검색창에서 아래와 같은 결과를 확인할 수 있었다. 응용하면 연관 검색어 및 검색어 태그 등 다양한 결과가 노출되도록 할 수도 있을 것이다.