본문 바로가기
  • Seizure But Okay Developer
강의 | 대외활동 | 개인플젝/마스크 재고 관리_개인플젝

마스크 재고 관리 프로젝트 - 2

by Sky_Developer 2020. 3. 30.

개요

바이러스가 만연한 현재에 개발자로써 주변사람들을 도울 수 있는 방법이 없을까 고민을 하다가, 마스크 재고를 알려주는 사이트를 만들어야 겠다 결심하였고 프로젝트에 대해 정리하는 글을 작성하였습니다.

 

프로젝트 글은 총 5편으로 나뉘어 작성될 예정이며 이번 글은 프론트엔드에 대한 내용을 담습니다.

 

목차

  • 사용자 위치 Data 유의사항
  • 프론트엔드 작업
    • 작업 순서 
    • 데이터 확인 
    • 퍼블리싱
      • 재고상태
      • 화면 구성 요소 배치
      • 반응형으로 만들기
    • JS 로 요소 렌더링 하기
      • 요소 추가하기
      • 요소 삭제하기
      • 상태관리
  • 결과물
  • 추가사항
  • 출처

 

사용자 위치 Data 유의사항

Google Doc에서 Geolocation API 를 사용할 때 유의할 점에 대해 설명해준 글을 찾아서 정리했다.

 

핵심 내용

  • 사용자에게 거부감이 들지 않도록 하기
    • 위치정보가 필요한 이유를 명확하게 설명하기 (요청 이유, 얻게되는 이점 등)
    • 사용자가 직접 액세스 요청에 대한 권한을 제공하도록 유도하기 (페이지가 로드되자마다 요청하지 않기)
  • 만약 사용자가 위치정보를 제공하기 원치 않을 경우 이에 대한 예외처리를 하자
    • 대체 솔루션 사용
  • 위치정보를 알맞게 사용하기
    • 위치정보를 더 이상 사용하지 않으면 clearWatch를 쓴다
    • 오류를 적절히 처리한다
  • Chrome DevTools로 임의로 위치정보를 주어 앱 테스트

 

프론트엔드 작업

 

작업 순서

  1. 퍼블리싱 (HTML 마크업 언어의 뼈대를 잡고, CSS 속성을 적용)
  2. 작성한 마크업 언어의 태그 순서에 따라 동적으로 JS 파일로 요소 생성

 

설명을 시작하기 앞서, 프론트엔드 작업을 하면서 얻은 결론은 다음과 같다.

 

퍼블리싱 작업을 완전하게 끝낸 다음에 기능 작업을 하자

 

왜냐하면, 퍼블리싱을 제대로 끝내지 않고 js 로 기능작업을 하니까 html 구조가 바뀔 때마다 js로 작업한 것 또한 같이 바꿔줘야 했으므로 매우 번거로웠다. 그러므로 다른 분들은 퍼블리싱을 마무리 짓고, 그 다음에 기능 작업에 들어가길 권유드린다.

 

데이터 확인

공공데이터 포털 API를 통해 어떤 데이터를 제공받는지 확인해보면 아래와 같다.

 

storesByGeo API

  • count : 약국수
  • code : 식별 코드
  • name : 이름
  • addr : 주소
  • type : 판매처 유형
  • lat : 위도
  • lng : 경도
  • stock_at : 입고 시간
  • remain_stat : 재고 상태
    • 100개 이상(녹색): 'plenty'
    • 30개 이상 100개미만(노랑색): 'some'
    • 2개 이상 30개 미만(빨강색): 'few'
    • 1개 이하(회색): 'empty'
    • 판매중지: 'break'
  • created_at : 데이터 생성 일자

 

데이터를 보면서 다음과 같은 기능들을 만들면 좋겠다는 생각이 들었다.

  • 주소 : 판매처 주소를 주요 포털 지도와 연결해서 클릭하면 위치를 확인할 수 있음
  • 입고 시간 : 언제 입고가 되었는지 확인
  • 데이터 생성 일자 : 언제 데이터가 업데이트 됬는지
  • 반경선택 : 최대 5000m 반경까지 선택할 수 있도록 함
  • 재고순, 거리순으로 정렬 : 재고가 많은 판매처 순, 현재 위치에서 가까운 판매처 순으로 정렬해서 보여준다
  • 재고 상태 : 현재 재고 상태 확인

또 마스크 재고를 한눈에 확인하기 쉽게 만들고 싶었는데 공공 데이터 포털에 나와있는 아래 내용을 눈여겨 보았다.

 

https://app.swaggerhub.com/apis-docs/Promptech/public-mask-info/20200307-oas3#/

 

이를 기반으로 수량 데이터를 색깔별로 표시해주기로 했다.

 

퍼블리싱

퍼블리싱 작업은 개인 포폴 사이트 디자인과 '모던 웹 사이트 디자인' 를 많이 참고해서 작업을 하였다.

 

재고상태

재고상태는 신호등처럼 표시 하기 위해 아래와 같이 설정했다.

  • border-radius : 50% (둥글게 보이기)
  • background : 초록 | 노랑 | 빨강 | 회색 | 검정
  • background-image : radial-gradient (라임 | 주황 | 갈색 | 흰색 | 회색)

background-image를 사용한 이유 : background 만 사용했을 때 색깔이 너무 단조로워서 형광색 느낌이 입혀지도록 그라디언트 옵션을 줌.

 

background-image : radial-gradient

radial-gradient는 원형 그라디언트를 그려내는 CSS 함수로 gradient 데이터 타입의 이미지를 반환한다. 위치도 지정할 수 있는데 default로 center가 설정되어 있다.

 

속성을 적용할 때 background 속성이 background-image 보다 상위에 있어야 한다. 반대로 되어 있으면 background 속성 색깔이 background-image의 색을 덮어버려서 원하는 느낌을 낼 수 없다.

 

결과적으로 다음과 같은 색깔로 보여지게 만들 수 있었다.

 

결과

화면 구성 요소 배치

요소들을 가로로 배치할 때 주로 float 를 사용하였다. float에 관한 자료는 앞서 언급한 책 또는 다음 글을 참고하길 바란다.

https://ktpark1651.tistory.com/203?category=681949

 

줄 바꾸기

총 판매처 수와 현재 마스크가 실제 입고 되어있는 판매처를 구분해서 보여주기 위해 텍스트에 개행 문자(\n)를 넣었다. 하지만 개행 문자를 일반 텍스트로 인식하면서 제대로 줄바꿈이 안됬고 이를 해결하기 위해 CSS의 white-space 를 알아보았다.

 

white-space에 여러 속성이 있지만 개행문자, <br> 태그, 가로 공간이 가득차 있을 때 줄바꿈을 해주는 속성은 다음 4가지가 있다.

  • pre
  • pre-wrap
  • pre-line
  • break-spaces

이 중 pre-wrap pre-line, break-spaces는 한 줄이 너무 길 경우 자동으로 줄 바꿈을 해준다.

 

  • pre-wrap : 줄바꿈 문자가 여러 개일 경우 각각 개별로 줄바꿈을 해준다.
  • pre-line : 이와 반대로 줄바꿈 문자가 여러 개일때 한번의 줄바꿈으로 합친다.
  • break-spaces : pre-wrap과 비슷하지만 공백이 마지막에 위치했을 때 공간을 차지하는 것으로 처리한다, 따라서 max-width 또는 min-width로 박스 크기를 조정할 때 영향을 준다.

textarea 태그를 사용할 땐 pre-wrap을 사용하는 것을 권한다. 보통 사람들은 단락을 구분하기 위해 Enter 키를 여러번 누른다. 이 때 pre-line을 적용하면 줄 바꿈을 지우게 되므로 개행을 유지하는 pre-wrap을 적용하는 것이 맞다. 

 

결론

자동으로 줄 바꿈을 해주고 개별 줄바꿈을 유지해주는 pre-wrap을 사용하기로 결정.

 

반응형으로 만들기

이 프로젝트는 브라우저 사이트 뿐만 아니라 모바일에서도 확인할 수 있게끔 만들기 때문에 미디어 쿼리를 사용했다.

화면을 줄이면서 요소 간에 이상한 행간이 생기는 것을 잡기 위해 margin, 폰트 크기, float 취소 등을 하였다.

 

무엇보다도 중요하다고 느낀 것은 퍼블리싱 작업을 완료한 다음에 진행해야 한다 는 것이다.

 

요소가 추가될 때마다 기존에 설정해놓은 값들에서 문제가 생겨 매번 수정 작업을 거치는게 여간 일이었다. 화면을 구성하고 또 반응형으로 만들고자 하는 사람들은 먼저 퍼블리싱 작업을 마무리 한 다음에 진행하길 권유한다.

 

Javascript로 기능 구현

기능은 순수히 javascript로만 구현하였다.

 

서비스 구성 요소는 다음과 같다.

  • 현재 위치 근처에 있는 판매처 찾기 기능 - 반경 지정 가능(version 1)
  • FAQ 기능(version 2)
  • 주소 검색으로 찾기 기능(version 3)

각 기능은 서비스 이용자의 요구에 따라 순차적으로 개발이 되었다.

 

그래서 React 같은 라이브러리나 여타 프레임워크를 사용하지 않은 이유는

  • 초기에는 현 위치에서 판매처를 찾는 기능만 있어서 라우팅, 렌더링 퍼포먼스 등을 크게 고려할 필요가 없었음
  • 초기 세팅 비용 등을 고려했을 때 vanila javascript로 하면 훨씬 부담이 적음

로 정리할 수 있다.

 

전체 동작 방식 (현 위치 판매처 찾기)

  • 유저가 업데이트 버튼을 클릭했을 때 유저의 위치정보를 받고 이를 통해 판매처 데이터를 받아옴
    • API 데이터를 받아오고 렌더링 하는 동안 loading GIF를 보여줌
    • null 데이터는 필터링하기
  • 옵션(반경, 정렬 기준 - 재고순, 거리순)을 받아서 동적으로 요소들을 렌더링 함
    • 총 판매처 수 / break(판매중지)인 판매처를 제외한 판매처 수 표시
    • 재고순 일 경우 plenty, some, few, empty, break 순으로 정렬
    • 거리순 일 경우 haversine 공식을 이용하여 정렬
      • 위도, 경도를 비교하여 거리를 계산하는 공식
    • 데이터 업데이트 및 입고 시간을 계산하여 Day, Hour, Minute 순으로 보여주기
    • 계산된 시간 : 현재 시간 - 데이터 시간
      • N : 계산된 시간 / 1000 * 60 * 60 * 24 >= 1
      • N 시간 : 계산된 시간 / 1000 * 60 * 60 >= 1
      • N : 계산된 시간 / 1000 * 60 >= 1
    • 재고 상태에 따라 다른 색깔로 표시하기
      • plenty - 초록색, some - 노란색, few - 빨간색, empty - 회색, break - 검은색
    • 주소를 클릭하면 특정 포탈 사이트의 검색 페이지로 이동
      • encodeURI를 이용하여 인코딩한 주소를 a link로 연결

 

JS 로 요소 렌더링하기

화면 렌더링은 주로 DOM API 를 이용해 요소들을 동적으로 생성해서 그려주었다.

 

요소 추가하기

화면 렌더링을 할 때 부모 요소에 자식 요소를 대입하기 위해 여러 DOM API 중 appendChild 메서드를 주로 사용하면서 그렸다.

Tip : javascript로 동적으로 만든 요소들에게 class 속성을 주기 위해선 DOM 요소의 classList 속성 중 add 메서드를 사용해야함. 만일 className에 클래스 속성을 문자열로 할당하면 제대로 적용이 안됨!

 

부모 요소에 자식 요소를 대입하는 DOM API는 appendChild 외에 append 도 있다. 둘의 차이는 굉장히 크니 잘 알아두는게 좋다.

 

  • append
    • Parent 노드 마지막 자식 뒤에 Node 객체 또는 DOMString 객체 삽입
    • 반환값 없음
    • 인자로 여러 개 노드와 문자 추가할 수 있음
  • appendChild
    • Parent 노드의 마지막 자식 뒤에 Node 객체를 삽입해줌 (Node 객체만 허용됨, DOMString 객체 X)
    • 추가한 Node 객체 반환
    • 단 하나의 노드만 추가할 수 있음
    • 만약 인자로 주어진 노드가
      • DOM 상에서 이미 존재하면서
      • 어떤 부모 노드 밑에 있다면
    • 현재 위치에서 새로운 위치 로 노드를 옮겨줌 (한 노드가 문서상의 두 지점에 동시에 존재할 수 없음을 의미)
      그래서 기존 Parent 노드에서 새로운 Parent 노드로 옮길 때 삭제하지 않아도 됨

 

요소 삭제하기

Learning Javascript - 18장 "브라우저의 자바스크립트" 를 보면 요소를 삭제하는 방법에 대해 알려주는데 다음과 같다.

 

Caution_ textContent나 innerHTML을 수정하는 것은 파괴적인 작업 입니다. 이 프로퍼티를 수정하면 원래 콘텐츠는 전부 사라집니다. 예를 들어 <body>나 innerHTML을 수정하면 페이지 전체 콘텐츠가 바뀝니다.

 

그러므로 먼저 div 내에서 판매처 목록을 그리고 하고, 다시 화면을 업데이트 해야할 땐 해당 요소의 innerHTML을 비우게 하면 된다. 예를 들면,

  1. <div class="list"> ... </div> 와 같이 div 태그 내에서 요소를 그린다
  2. 목록을 갱신한다던지 요소를 삭제해야 할 때, list 요소의 innerHTML을 비워준다
    1. document.querySelectorAll('.list')[0].innerHTML = ''

 

참고로, 태그간의 depth가 어느정도 있으면 appendChild로 요소를 집어넣는 작업이 복잡해질 수 있다. 또 기능을 추가하기 위해 html 구조를 수정까지 하면 depth를 고려했던 기존의 코드들 또한 섞여버리므로 일이 두배가 된다. 필자는 이래서 프레임워크를 쓰는구나 라고 느꼈었다. 

 

그러므로, 퍼블리싱을 완전하게 마무리 짓고 기능 작업을 하는 것이 좋다.

 

상태 관리

추가 기능 중 주소 검색으로 찾기를 할 때 읍/면/동 까지만 입력해주어야 판매처 찾기가 됬다. 개발을 하다보니 주소 텍스트 값을 관리하는 것이 고민되었다.

  • 주소 API에서 제공하는 추천 주소 텍스트 값
  • 유저가 입력한 주소 텍스트 값

주소 텍스트의 상태는 하나지만 두 곳에서 영향을 받기 때문에, 이를 자연스럽게 동작하게끔 관리하는 방법을 고민하다 class 를 적용하기로 했다.

 

  • Address.js 파일을 만들어 주소 텍스트 값을 내부 변수로 갖는 Address Class를 생성
  • Weakmap 을 적용해서 키로 참조하는 객체가 더 이상 존재하지 않을 경우 Garbage Collector가 메모리 소거하도록 함

문제 : 한번 검색을 후 input 필드를 비운 뒤 다시 검색을 하면 경고 문구 대신 이전 텍스트로 검색이 됨

원인 : input 요소의 이벤트 리스너 함수에 setTimeout 을 적용시켜서 Address text 값을 초기화 시키는 것이 상대적으로 늦음

해결 : 검색 버튼을 누를 때 input 필드가 비어있는지 확인. 비어있다면 사용자가 의도를 갖고 비운 것이라 판단하고 Address text 값을 empty string으로 바꿈 -> 에러 문구 보여짐

 

한계점1 : Address text의 상태를 class로 만들어 관리를 하지만, 다른 모듈 간에 class 값을 효율적으로 전달받으면서 사용하지 못함 (export, import 사용)

 

한계점2 : DOM 요소를 만들어내는 코드는 여전히 길고 복잡하기 때문에 DOM 요소를 생성하는 코드 또한 클래스 스타일로 바꿔줘야 함 (하지만 동적으로 요소를 만들면서 클래스 스타일을 적용하기에는 아직 시간이 걸릴 듯함)

 

결과물

Git 저장소 : https://github.com/SkynI25/mask-stock

사이트주소 : https://skyni25.github.io/mask-stock/

 

추가사항

Mixed Content 오류

개념 : 페이지 내 HTTP 콘텐츠와 HTTPS 콘텐츠가 같이 로드되면서 페이지를 표시할 때 나타나는 현상

HTTPS 컨텐츠를 제공할 때 css 파일 또는 js 파일을 http 를 통해 받아오면 Mixed Content 오류가 생긴다.

 

해결방법 : http 링크로 받아오는 리소스는 https 링크로 받아오도록 수정하자. 관련된 자세한 내용은 아래 링크를 참고

https://developers.google.com/web/fundamentals/security/prevent-mixed-content/fixing-mixed-content?hl=en

 

 

Geolocation API 추가정보

개념 : Geolocation API 에서 권한을 물어본 후 browsing session이 바뀌기 전까지 권한을 기억한다

 

작업 후 글을 쓰면서 고찰해보니, 초기에 권한정보를 기억하기 위해 사용한 Permission API가 굳이 필요하지 않다는 것을 알았다 (블로그 글은 수정했습니다) 공식문서에서 Geolocation API 메서드 자체에서 browsing session이 유지될 때까지 권한을 기억한다고 설명되어 있었다. 실제로도 사용자 권한을 한번 허락한 다음부턴 권한을 기억하고 있었으며 캐시를 지우거나 새로운 브라우저 세션(새로운 시크릿 탭으로 접속)으로 접속한 경우에 다시 권한을 물어보았다.

 

초기에 로컬(url 입력창이 file url)에서 테스트했기 때문에 권한을 여러번 물어보는 현상이 있어서 이와 같이 착각했던 것 같다. 더 자세한 내용을 알고 싶은 분은 아래 url을 참고하길 바란다.

 

Geolocation API DOC

 

 

 

여기까지가 마스크 재고 프로젝트와 관련된 프론트엔드 작업이었고, 다음 글에는 서버세팅과 CS 작업에 관하여 다뤄보겠습니다. 감사합니다.

 

출처

https://developer.mozilla.org/ko/docs/Web/API/Element/classList

https://codepen.io/A973C/pen/hnEaf

https://lernabit.com/cards/notecard/Q9kuMv2bYWrfclH

http://www.movable-type.co.uk/scripts/latlong.html

https://kayuse88.github.io/haversine/

https://en.wikipedia.org/wiki/Haversine_formula

https://developer.mozilla.org/ko/docs/Web/CSS/white-space

http://jennifermadden.com/javascript/lineBreaks.html

https://developers.google.com/web/fundamentals/security/prevent-mixed-content/fixing-mixed-content?hl=en

댓글