Firebase Web 채팅앱 만들기 - 8. Realtime Database를 이용한 채팅기능 구현 - 채팅화면 및 채팅메세지 리스팅

1. 코드 작성

  • 유저리스트 화면에서 유저를 클릭하면 채팅방을 오픈하는 코드를 작성
    • 코드 설명 :
      • 유저리스트 데이터를 받아 화면을 그리는 getUserList 메소드에서 화면을 그리고 난 뒤 유저별로 클릭 이벤트를 바인딩 하는 코드를 입력.
      • 클릭시 수행되는 코드는 onUserListClick 메소드 입니다.
      • onUserListClick 메소드는 아직까지는 단순하게 숨겨져있던 백버튼과 친구 초대버튼을 보이게 하고 openChatRoom메소드를 통해서 채팅방 화면을 보이게 하는 역할을 수행
      • 채팅방 화면은 앱에서 보이는 탭은 3개 뿐이나 보이지 않는 4번째 탭 영역에 메세지를 볼수 있는 채팅방 화면을 숨겨져있음.
      • 숨겨져있는 4번째 탭을 클릭 함으로써 채팅방 화면을 노출.
      • isOpenRoom 변수는 방이 오픈된 상태인지 아닌지 구분하는 변수
      • 추후에 상단 타이틀을 갱신할 때 이 값을 이용하게 됩니다.
         /**
           * 초기 필드 변수 할당
           */
          FirebaseChat.prototype.init = function(){
               ... (생략)
              this.tabMessageList = document.getElementById('tabMessageList');
              this.aBackBtn = document.getElementById('aBackBtn');
              this.aInvite = document.getElementById('aInvite');
          }
          
          /**
           * loadUserList 에서 데이터를 받아 왔을 때
           */
          FirebaseChat.prototype.getUserList  =  function(snapshot) {
              //...생략 

              /**
               *  유저리스트 클릭 이벤트 적용
               */
              var arrLiList = this.ulUserList.getElementsByTagName('li')
              var arrLiListLength = arrLiList.length;
              for(var i=0; i < arrLiListLength; i++){
                  arrLiList[i].addEventListener('click', this.onUserListClick.bind(this));
              }

          }

          /**
           * 유저리스트 클릭
           */
          FirebaseChat.prototype.onUserListClick = function(event){
              this.aBackBtn.classList.remove('hiddendiv'); // 백버튼 노출
              this.aInvite.classList.remove('hiddendiv'); // 초대 버튼 노툴
              this.openChatRoom();
          }

          /**
           * 챗방 오픈 , 메세지 로드 및 AddUserList
           */
          FirebaseChat.prototype.openChatRoom = function(){
              this.isOpenRoom = true;  // 방이 열린 상태인지 확인하는 플래그 값
              this.tabMessageList.click(); // 채팅방 화면 보이기
          }
  • 메세지를 불러오는 코드 작성
    • 코드 설명 :
      • 유저리스트를 클릭하는 시점에서 방 ID 값을 생성.
      • init메소드에서 새롭게 추가한 MAKEID_CHAR, DATETIME_CHAR 상수는 방 ID 값을 생성할때 구분자로 사용하기 위한 상수.
      • Firebase Realtime Database의 특성상 여러 항목의 조건을 한꺼번에 조회하기 어려운 경우에는 여러 항목의 값을 하나의 값으로 구분자를 중간에 두고 값을 조합해 저장하기도 합니다.
      • 현재 방 ID 는 구분자를 앞에 두고 방을 생성한사람의 UID와 생성한 시점의 날짜시간값의 조합으로 되어 있습니다.
      • 방 ID(roomId) 값이외에 유저리스트를 클릭하는 시점에서 방의 참여 인원의 ID(roomUserList), 방의 참여인원 이름(roomUserName), 방제목(roomTitle)값이 함께 세팅 됩니다.
      • 이런 방의 기본 정보가 세팅이 되고 채팅방을 openChatRoom메소드에서 열게 됩니다.
      • openChatRoom메소드에서 화면 상단 제목을 바꾸고, 메세지를 읽어드리며, 방이 open 되어있는지 상태값을 저장할 변수에 true 값을 할당하게 됩니다.
      • 실질적으로 메세지를 읽어오는 loadMessageList메소드는 limitToLast 메소드를 통해서 Messages 아래 방ID 아래 하위 50개의 데이터만 데이터를 불러옵니다.
      • ‘child_added’ 이벤트는 처음은 기존 데이터를 불러오고 그 다음부터는 새롭게 추가된 데이터가 있을 경우에만 데이터를 수신하는 이벤트 입니다.
      • timestampToTime 메소드는 FirebaseChat클래스에 static 형태로 선언된 메세지의 timestamp 형태의 입력 시간을 우리가 읽을 수 있는 일반적인 날짜 시간 형태로 변환하는 유틸 메소드입니다.
      • 데이터를 불러와 화면을 그리게되는 방식은 앞서 유저리스트를 그렸던 방식과 같이 underscore template메소드를 활용하게 됩니다.
          /**
           * 초기 필드 변수 할당
           */
          FirebaseChat.prototype.init = function(){
              //...생략
              this.MAKEID_CHAR = '@make@';
              this.DATETIME_CHAR = '@time@';
              this.spTitle = document.getElementById('spTitle');
              this.ulMessageList = document.getElementById('ulMessageList');
          }

          /**
           * 유저리스트 클릭
           */
          FirebaseChat.prototype.onUserListClick = function(event){
              //...생략 
              var targetUserUid = event.currentTarget.getAttribute('data-targetUserUid');
              var targetUserName = event.currentTarget.getAttribute('data-username');
              this.roomTitle = targetUserName+'';
              this.roomUserlist = [targetUserUid, this.auth.currentUser.uid]; // 챗방 유저리스트
              this.roomUserName = [targetUserName, this.auth.currentUser.displayName] // 챗방 유저 이름
              this.roomId = this.MAKEID_CHAR + this.auth.currentUser.uid + this.DATETIME_CHAR + FirebaseChat.yyyyMMddHHmmsss();
              this.openChatRoom(this.roomId, this.roomTitle);  // 파라미터 추가
          }

          /**
           * 챗방 오픈 , 메세지 로드 및 AddUserList
           */
          FirebaseChat.prototype.openChatRoom = function(roomId, roomTitle){ // 파라미터 추가
              this.isOpenRoom = true; // 방이 열린 상태인지 확인하는 플래그 값
              if(roomTitle){  //상단 타이틀 변경
                  this.spTitle.innerHTML = this.roomTitle;
              }
              this.loadMessageList(roomId); //메세지 로드
              this.tabMessageList.click();
          }

          /**
           * 메세지 로드
           */
          FirebaseChat.prototype.loadMessageList = function(roomId){

              if(roomId){
                  this.ulMessageList.innerHTML = ''; //메세지 화면 리셋
                  var messageTemplate = document.getElementById('templateMessageList').innerHTML;
                  if(this.messageRef){  // 이전 메세지 ref 이벤트 제거
                      this.messageRef.off();
                  }
                  this.messageRef = this.database.ref('Messages/' + roomId);
                  var cbDisplayMessages = function(data) {
                      var messageHtml = '';
                      var val = data.val();
                      messageHtml = _.template(messageTemplate)({
                          key : data.key
                          , profileImg : val.profileImg
                          , userName : val.userName
                          , time : FirebaseChat.timestampToTime(val.timestamp)
                          , message : val.message
                      });
                      this.ulMessageList.innerHTML = this.ulMessageList.innerHTML + messageHtml;
                      this.ulMessageList.scrollTop = this.ulMessageList.scrollHeight;
                      this.roomTitle = val.roomTitle;
                  }
                  this.messageRef.limitToLast(50).on('child_added', cbDisplayMessages.bind(this));
              }
          }
          /**
           * 현재날짜 yyyyMMddHHmmsss형태로 반환
           */
          FirebaseChat.yyyyMMddHHmmsss =function(){
              var vDate = new Date();
              var yyyy = vDate.getFullYear().toString();
              var MM = (vDate.getMonth() + 1).toString();
              var dd = vDate.getDate().toString();
              var HH = vDate.getHours().toString();
              var mm = vDate.getMinutes().toString();

              var ss = vDate.getSeconds().toString();
              var sss= vDate.getMilliseconds().toString();
              return yyyy + (MM[1] ? MM : '0'+MM[0]) + (dd[1] ? dd : '0'+dd[0]) + (HH[1] ? HH : '0'+ HH[0])
                  + (mm[1] ? mm : '0'+ mm[0]) + (ss[1] ? ss : '0'+ss[0])+ sss;
          };
          
          /**
           * timestamp를 날짜 시간 으로 변환
           */
          FirebaseChat.timestampToTime = function(timestamp){
              var date = new Date(timestamp),
                  year = date.getFullYear(),
                  month = date.getMonth()+1,
                  day = date.getDate(),
                  hour = date.getHours(),
                  minute = date.getMinutes(),
                  week = new Array('', '', '', '', '', '', '');

              var convertDate = year + ""+month+""+ day +"일 ("+ week[date.getDay()] +") ";
              var convertHour="";
              if(hour < 12){
                  convertHour = "오전 " + FirebaseChat.pad(hour) +":" + FirebaseChat.pad(minute);
              }else if(hour === 12){
                  convertHour = "오후 " + FirebaseChat.pad(hour) +":" + FirebaseChat.pad(minute);
              }else{
                  convertHour = "오후 " + FirebaseChat.pad(hour - 12) +":" + FirebaseChat.pad(minute);
              }

              return convertDate + convertHour;
          }

          /**
           *  10미만 숫자 앞에 0 붙이기
           */
          FirebaseChat.pad = function(n){
              return n > 9 ? "" + n: "0" + n;
          }