Jindorry-Album [html, css, js] | Step 2.

진도리·2022년 2월 1일
0

Jindorry-Album

목록 보기
2/5
post-thumbnail

Process

  1. 어떻게 로컬 서버를 생성할지?
  2. 클라이언트 <-> 서버 통신
  3. HTML
  4. JS
  5. CSS
  6. Server

1. 로컬 서버 생성

  • javascript 런타임 모듈인 Node.js를 사용하는 웹(서버)프레임워크인 Express.js 를 사용해서 서버(로컬)를 구성
folder-structure

└ 폴더 구조

  • code 📁
    css, html, js 파일 관리
  • node_modules 📁
    express 설치시 기본적으로 설치되는 모듈들 & 추가 설치한 npm 모듈
  • public 📁
    사람들에게 보여줘도 괜찮은 파일들을 관리... 주로 이미지 파일들
  • app.js 📝
    Express 서버 동작구현 코드
  • package.json 📝
    설치된 모듈의 버전 관리 (정확한 버전 명시 보단 버전의 범위를 명시)
  • package-lock.json 📝
    package.json 만으로는 설명이 어려운 모듈의 정확한 버전이나 더 narrow한 범위를 명시


2. 클라이언트 <-> 서버 통신

  • 년/월 방식으로 이미지 폴더를 관리 ex) public/image/2021/07/001.jpg
  • step 1. 에서는 이미지 파일들의 리소스를 html 코드에 명시해 주었지만, step 2. 에서는 로컬 서버를 생성하여 동적으로 이미지 파일을 받아오기로 했음
  • html 코드에 <div>, <img>, 사진 이미지 파일리소스의 코드를 지워줌
  • api 설계
메소드경로요청데이터응답데이터설명
1GET/num-of-filesyy:년도, mm:월fileList.length: 파일개수이미지 파일 개수를 받아옴
2GET/fileidx: 인덱스fileList[idx]: idx번째 파일순서대로 파일을 하나씩 받아옴

이에 따라 필요한 과정을 순서대로 읊자면 ...

(1). 달력에서 확인하고자 하는 앨범의 년/월을 선택 -> 'onchange' 이벤트 생성
(2). 'onchange' 이벤트에 선택된 년/월의 파일을 받아오는 http 요청 코드 구현
(3). 모든 년/월에 해당하는 폴더가 존재하는 것이 아니였기에, 존재하는 경우와 존재하지 않는 경우에 대한 핸들링이 필요

  • 해당 년/월에 폴더가 존재하면 그대로 안에 있는 파일들을 읽어서 요청에 대한 응답으로 파일들을 전송
  • 해당 년/월에 폴더가 존재하지 않으면 폴더만 생성하고 빈 상태로 둠
    - 폴더를 미리 손수 만들어 놓기엔 앨범이라는 것의 시간적 범위가 너무 넓음
    - 년도폴더 또는 월별폴더가 존재하는지에 대한 컴파일과정을 최소화 하기 위해, 없는 년/월 폴더를 처음으로 요청 했을 때 해당 파일루트를 만들어 놓음

(4). 년/월 폴더에 대한 확인이 끝나면, 해당 폴더에 이미지 파일이 있는지 없는 지 확인

  • 파일이 존재하면 파일을 화면에 추가
  • 파일이 존재하지 않으면 없다고 명시


3. HTML

index.html 📝

<body>
    <h1>진도리의 앨범</h1>
    <br />
    <p id="refer">년/월 선택</p>
    <br />
    <input
      type="month"
      id="calendar"
      name="calendar"
      onchange="loadBtnHandler(this.id)"
    />
    <br />
    <script src='../js/index.js'></script>
  </body>
  • step 1. 에서의 html 코드보다 <body> 부분의 코드길이가 확실히 줄어듬
  • 달력폼을 제외하고 다른 <div> 또는 <img> 들을 없애 줌
  • 대신 연결한 index.js로 HTML에 동적으로 이미지 파일을 dom.body에 추가 해주는 코드가 추가되거나(이미지 파일 개수 > 0) 삭제됨(이미지 파일 개수 == 0)


4. JS

index.js 📝

let baseUrl = 'http://localhost:3001/';

const getFileLen = async (yy, mm) => {
  const response = await fetch(baseUrl + 'num-of-files', {
    method: 'GET',
    headers: {
      yy: yy,
      mm: mm,
    },
  });
  if (!response.ok) {
    alert("Error : Function Name 'getFileLen'");
    return;
  }
  return await response.json();
};

const getFiles = async (idx) => {
  const response = await fetch(baseUrl + 'file', {
    method: 'GET',
    headers: {
      idx: idx,
    },
  });
  if (!response.ok) {
    alert("Error : Function Name 'getFiles'");
    return;
  }
  return await response.blob();
};

const loadedImgExist = () => {
  for (let node of document.body.childNodes){
    if (node.id === 'loadedImg'){
      return true
    }
  }
  return false
}

const loadBtnHandler = async (id) => {
  if(loadedImgExist()){
    document.getElementById('loadedImg').remove();
    //document.body.removeChild(document.getElementById('loadedImg')); 같은역할
  }

  let ym = document.getElementById(id).value;

  if(!ym) return; // 달력 삭제 버튼 눌렀을 경우

  ym = ym.split('-');
  let div = document.createElement('div');
###   div.id = 'loadedImg'

  getFileLen(ym[0], ym[1]).then((fileLen) => {
    if (fileLen > 0) {
      div.style =
        'margin: 0 auto;height: 130px;width: 90vw;white-space: nowrap;overflow-x: scroll;display: block;';
      for (let i = 0; i < fileLen; i++) {
        getFiles(i).then((result) => {
          let img = document.createElement('img');
          img.style =
            'height: 100px;width: 200px;object-fit: scale-down;display: inline-block;';
          img.src = URL.createObjectURL(result);
          img.alt = `loaded image num ${i + 1}`;
          div.appendChild(img);
        });
      }
    } else {
      // 해당되는 파일이 없으면 없다고 표기
      div.style = 'text-align:center;'
      div.innerHTML = '이미지가 존재하지 않습니다.';
    }
  }).then(() => {
    document.body.append(div);
  });
};
  • index.js 에는 3개의 비동기 함수로 서버로 요청을 보냄

    async loadBtnHandler()
    달력의 년/월 정보가 변경되면 (onchange event) 해당 년/월 정보로 서버로 이미지 파일을 받아오는 함수

    (1). loadedImgExist() 로 앨범 이미지 요청으로 인해 document.body 에 동적으로 추가된 div 정보가 있는지 확인 후, 있으면 해당 정보를 documnet.body 에서 제거
    (2). 달력의 년도 및 월에 해당하는 이미지 파일의 길이를 getFileLen() 로 받아옴
    (3). await 할 서버로의 요청이 존재하지 않지만, 파일을 받아오는 비동기 통신이 모두 끝난 후에 프로미스를 사용해서 생성된 이미지 컨테이너를 document.body 에 추가하기 위해 비동기 함수로 작성

    async getFileLen()
    년/월 정보로 서버로 해당 년/월 폴더에 있는 이미지 파일의 개수를 받아오는 함수

    (1). 파일의 개수가 하나 이상이면, getFiles() 로 이미지 파일을 하나씩 받아와서 이 파일들을 화면에 추가 해주기 위한 스타일 적용 및, <img> 속성값을 작성
    (2). 파일이 존재하지 않으면 없다고 표기

    async getFiles()
    파일개수 만큼 하나씩 이미지 파일을 서버에서 받아오는 함수

    (1). 서버에서 res.sendfile('image_path'); 로 파일을 받아오면 해당 파일을 blob(binary large object)형태로 반환
    (2). 반환된 blob 파일을 새로운 객체 url로 생성하여 이 url로 <img> 의 src로 사용

before-req page-inspection

└ 페이지 검사 시 나오는 화면 (이미지 요청 전)

after-req page-inspection

└ 페이지 검사 시 나오는 화면 (이미지 요청 후)



5. CSS

index.css 📝

h1 {
  text-align: center;
}

#emptyImg {
  text-align: center;
}

#refer {
  margin: 0 auto;
  text-align: center;
}

#calendar {
  margin : 5px;
  margin: 0 auto;
  display: block;
}
  • 서버에서 받아온 이미지파일들을 현재 dom 트리에 추가해주는 것일 뿐, 이미지를 서버에서 받아온들 전체 페이지를 리로드 하는 것이 아니기 때문에, 이미지 컨테이너를 생성 후에 index.css 에 있는 스타일을 적용하는 것이 불가했음
  • 동적으로 렌더링할 이미지 컨테이너를 생성해서 스타일을 그때마다 적용하기 때문에, 이 이미지 컨테이너에 대한 스타일을 step 1. 에서 제거해주었기에, css 코드 길이가 전보다 줄어듬


6. Server

app.js 📝

  • 설명에 필요한 코드만을 작성!
/* 폴더 유무 체크하고 없으면 폴더 생성 */
const checkFolder = (yr, mth) => {
  let yearPath = imagePath + yr;
  let monthPath = imagePath + yr + '/' + mth;
  if (fs.existsSync(yearPath)) {
    if (fs.existsSync(monthPath)) {
      // '년/월 폴더 둘다 존재'
    } else fs.mkdirSync(monthPath);
  } else {
    fs.mkdirSync(yearPath);
    fs.mkdirSync(monthPath);
  }
  return monthPath;
};

let monthPath = '';
let fileList = [];

/* 년/월 폴더의 파일 개수를 응답 */
app.get('/num-of-files', function (req, res) {
  monthPath = checkFolder(req.headers.yy, req.headers.mm);
  fs.readdir(monthPath, (err, files) => {
    if (err) {
      console.log(err);
      res.status(500).send('server error');
    }
    fileList = files.map((item) => path.join(monthPath, item));
    res.json(fileList.length);
  });
});

/* 순서대로 파일을 보내기 위해 idx 값을 사용하여 fileList[idx] 로 이미지 파일 전송 */
app.get('/file', function (req, res) {
  res.sendFile(fileList[req.headers.idx])
});

의외로 복병이었던 server 코드...

  • 선택된 년/월에 해당되는 이미지들이 존재하면 서버에서 이미지 파일들을 한번에 받기 위해 폴더에 있는 모든 파일들을 받아오려 했음
    (1). 파일 하나의 절대경로를 받아서 해당 파일을 전송하는 res.sendFile()은 있었지만 res.sendFiles()는 없었음
    (2). 아에 폴더를 받아오려 했지만 이것도 실패
  • 결국 이미지 파일을 하나씩 받아와서 다 받아오면 그 때 dom.body 에 해당 파일들을 담고 있는 이미지 컨테이너를 추가하는 코드를 작성
    (1). 먼저 폴더유무 체크하고 없으면 폴더 생성
    (2). fs 모듈로 해당 폴더에 접근해서 fileList에 이미지 파일의 절대 경로를 하나씩 넣어줌
    (3). 이미지 파일 개수를 클라이언트로 전송
    (4). req로 받아온 idx값을 통해 순서대로 이미지 파일을 전송

  • 예를들어 2021년 7월의 7개의 이미지 파일을 받아오기 위해선, 2021년 7월에 해당하는 폴더루트에 이미지 개수 7개를 응답할 때 1번, 순서대로 하나씩 이미지 파일을 전송하기 위해 7번. 총 8번의 fetch() 함수가 실행됨.



Conclusion

  1. 결론
  2. TakeAway
  3. Next Step?

1. 결론

  • step 1. 에서 step 2. 로는 단순히 보여줄 이미지를 미리 만들어 놓는 정적인 앨범에서 유저의 선택에 따라 해당되는 이미지를 만드는 동적인 앨범으로 바꾸기로 했을 뿐인데, 생각보다 추가 및 수정이 많아졌음...
  • 정적인 서비스에서 동적인 서비스로 갈 수록 html은 가벼워지고 js는 무거워지는 것 같은 기분이...


2. TakeAway

  • React.js 를 사용하여 웹페이지를 생성했을 때는 React 라이프 사이클, React-hook을 이용해서
    화면에 렌더링할 것들을 업데이트했던 것에 비하면 불편한 점들이 있었음

  • html, js, css 만을 사용해서 스크린을 새 스크린으로 업데이트 한다기 보단(rerender), 이벤트를 생성하여 이에 대한 핸들링을 통해 dom-tree에 동적으로 추가 및 삭제하는 것임

  • 단순 업데이트가 아닌 추가에 대한 삭제하는 코드도 곁들여야 하기에 코드 길이와 컴파일 시간이 길어질 수 있다는 생각이 들었음

  • blob (binary large object)
    - 바이너리 형태의 큰 객체
    - 주로 이미지, 비디오, 사운드 등과 같은 멀티미디어 객체들을 가리킴

  • URL.createObjectURL( file )
    - 특정 파일 객체나 로컬 파일 또는 데이터의 참조를 가리키는 새로운 객체 URL을 생성
    - 객체를 생성한 문서 내에서만 유효



3. Next Step

  • 이미지 삭제 기능 추가

profile
매일 작은 보폭이라도 앞으로.

0개의 댓글