ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Javascript 공부하기. 알람 시계 5. setTimeout 안에서 alert 활용하기.
    공부/컴 2017. 12. 10. 22:08

    갑자기 Javascript 로 허접한 알람 시계를 만드는 내용에 관한 글을 더 쓰게 되었다.


    분명 저번 4번쨰 글로 사실상 마무리를 한 셈이었는데 갑자기 추가하고 싶은 기능이 생각났고, 그것을 구현하는 것도 이 글의 연장선이 될 것 같아서 쓰게 되었다.


    일단... 저번에 만든 알람시계의 코드는 이렇다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    <!DOCTYPE html>
    <meta charset='utf-8'>
    <html>
      <head>
        <title>Alarm</title>
      </head>
      <body>
       <div id='DIValarmtable'></div> 
      </body>
    </html>
     
    <script type="text/javascript" src="jquery.js" charset="utf-8"></script>
     
    <script type='text/javascript'>
     
    var ALARM_NUMBER=5;
    var ALARM_SOUND_PATH="justice.mp3";
     
    function MakeTable(){
      var divspace=jQuery('#DIValarmtable')[0];
      var tcs="<table border=1>\
      <tr>";
     
      var tcs=tcs+"<td colspan=3><div id='DIVclock'></div></td>\
      </tr>";
     
      for(var i=0;i<ALARM_NUMBER;i++){
        var tcs=tcs+"<tr>\
        <td><div id='alarmlight"+i+"' align=center onClick='javascript:alarmclock["+i+"].toggle_enable()' style='color:#40FF40;'>●</div></td>\
        <td><audio id='alarmsound"+i+"' src='"+ALARM_SOUND_PATH+"'></audio>\
        <div id='DIValarm"+i+"'></div></td>\
        <td><div id='DIVremain"+i+"'></div></td>\
        </tr>";
      }
      var tcs=tcs+"</table>";
     
      divspace.innerHTML=tcs;
    }
     
    MakeTable();
     
    function Clock(){
      this.time=new Date();
      this.interface_div;
      this.runf=function(){
     
        this.time=new Date();
        jQuery('#DIVclock')[0].innerHTML=this.time;
      }//endof runf
    } //endof function Clock
     
    function Alarm(){
      this.time=new Date(0);
      this.number;
      this.alarmsound;
      this.interface_divalarm;
      this.interface_divremain;
      this.alarm_enabled=true;
     
      //알람 울리기.
      this.ringalarm=function(){
          this.alarmsound.play();
        alert((this.number+1)"번 알람입니다.");
     
      }
      //프레임마다 알람시계에서 작동되는 함수 runf
      this.runf= function(){
        //현재시간을 비교해서 알람을 울리도록 한다.
        var nowtime=new Date()
        if(this.time.getHours()==nowtime.getHours() &&
            this.time.getMinutes()==nowtime.getMinutes() &&
            this.time.getSeconds()==nowtime.getSeconds() &&
            this.alarm_enabled){
          this.ringalarm();
        }
        var atime=this.time.getHours()*3600 + this.time.getMinutes()*60 + this.time.getSeconds();
        var ctime=nowtime.getHours()*3600 + nowtime.getMinutes()*60 + nowtime.getSeconds();
        if(atime<ctime)atime+=24*60*60;
     
        //유효성 검사.
        if(typeof($('#remaintime'+this.number)[0])=='object'){
         $('#remaintime'+this.number)[0].innerHTML=parseInt((atime-ctime)/3600) + " : " + parseInt(((atime-ctime)%3600)/60) + " : " + (atime-ctime)%60 ;
        }
      }//endof runf
      //알람 설정
      this.submitalarm=function(){
          var hhh=$("#hhh"+this.number)[0].value;
          var mmm=$("#mmm"+this.number)[0].value;
          var sss=$("#sss"+this.number)[0].value;
     
          if(hhh<=0 || hhh>23 || isNaN(hhh)){hhh='00';}
          if(mmm<=0 || mmm>59 || isNaN(mmm)){mmm='00';}
          if(sss<=0 || sss>59 || isNaN(sss)){sss='00';}
     
          this.time.setHours(hhh);
          this.time.setMinutes(mmm);
          this.time.setSeconds(sss);
     
          this.interface_divalarm.innerHTML=hhh + " : " + mmm + " : " + sss;
          this.remaintimeform();
      }//endof submitalarm
      this.inputform= function(){
        var inputformstring="<form onsubmit='alarmclock["+this.number+"].submitalarm()'>\
        <input id='hhh"+this.number+"' type='text' size='2' maxlength='2' value="+this.time.getHours()+"> : \
        <input id='mmm"+this.number+"' type='text' size='2' maxlength='2' value="+this.time.getMinutes()+"> : \
        <input id='sss"+this.number+"' type='text' size='2' maxlength='2' value="+this.time.getSeconds()+">\
        <input id='setalarm' type='submit' value='OK' ></form>";
        this.interface_divremain.innerHTML=inputformstring;
      }//endof inputform
      this.remaintimeform= function(){
        var remainformstring="<div id=remaintime"+this.number+"></div>\
        <input type='button' value='알람설정' onclick='alarmclock["+this.number+"].inputform()'>";
        this.interface_divremain.innerHTML=remainformstring;
     
      }//endof remaintimeform
     
      this.toggle_enable=function(){
        if(this.alarm_enabled){
          this.alarm_enabled=false;
          $("#alarmlight"+this.number)[0].style.color="#FF0000";
        }//endof if this.alarm_enabled is true
        else{
          this.alarm_enabled=true;
          $("#alarmlight"+this.number)[0].style.color="#40FF40";
        }//endof else
      }//endof toggle_enable
     
    }//endof Alarm
     
    //초기화 함수
    var nowclock=new Clock();
    var alarmclock=new Array(ALARM_NUMBER);
    function Initialize(){
      nowclock=new Clock();
      nowclock.interface_div=jQuery('#DIVclock')[0];
     
      for(var i=0;i<ALARM_NUMBER;i++){
        alarmclock[i]=new Alarm();
        alarmclock[i].number=i;
        alarmclock[i].interface_divalarm=jQuery('#DIValarm'+i)[0];
        alarmclock[i].interface_divremain=jQuery('#DIVremain'+i)[0];
        alarmclock[i].alarmsound=jQuery('#alarmsound'+i)[0];
        alarmclock[i].inputform();
       alarmclock[i].submitalarm();
      }
    }
    Initialize();
     
    //Run Interval
    function RunInterval(){
     
      nowclock.runf();
      for(var i=0;i<ALARM_NUMBER;i++){
        alarmclock[i].runf();
      }
      setTimeout(RunInterval,50);
    }
    RunInterval();
    </script>
     
    cs


    이 알람시계 페이지를 실제로 쓰고 있었는데 (나는 내 필요에 따라 프로그래밍을 할 때가 많다.)


    두 가지 개선점이 필요해졌다.

    하나는 알람이 울릴 때 가끔 alert(); 함수가 먼저 발동해서 알람소리의 play(); 함수가 작동하지 않는 것 (alert 가 작동하는 동안은 페이지가 멈추어 있기 때문이다.)


    하나는 특정 시각에 알람을 울리는 기능만 구현되어 있고, 일정 시간 후에 (즉 1시간 후 알람) 같은 기능이 없어서 불편했다는 점이다.


    첫 번째 문제점을 수정하는 것은 간단해 보였다. 알람을 울리는 부분에 있는 alert() 에게 약간의 지연만 주면 될 것 같았다.


    1
    2
    3
    4
    5
      //알람 울리기.
      this.ringalarm=function(){
          this.alarmsound.play();
          setTimeout(alert((this.number+1)+"번 알람입니다."),1000);
      }
    cs


    그래서 코드를 이렇게 짰는데, setTimeout 은 무시하고 alert는 여전히 즉시 동작했다. 정확한 이유까지는 모르겠지만, setTimeout 안에서 alert 함수를 직접 쓰는 것은 불가능했던 모양이다.

    인터넷에 검색을 해 본 결과 다른 사람들은 모두 function(){ alert();} 를 사용하고 있었다. 그래서 나도 그렇게 바꾸었다.


    1
    2
    3
    4
    5
      //알람 울리기.
      this.ringalarm=function(){
          this.alarmsound.play();
          setTimeout(function(){alert((this.number+1)+"번 알람입니다.");},1000);
      }
    cs


    자 이렇게 하면 작동 하겠지!

    하지만 안타깝게도 이번엔 더 큰 에러를 뱉어냈다. 그 이유는 function(){ } 안에서 this. 를 이용해 상위 function 의 변수에 접근하려고 했기 때문이다. 

    이 this라는 놈은 특이한 놈이데, 새로운 function(){} 구간 안에 들어가면, 그 안에서 호출을 하는 것이지 밖의 함수에 접근하는 것이 아니라서 alarmclock 에 this로 접근 할 수는 없다.

    그 특이함을 여기에 일일히 설명하기에는 내 이해도 지식도 그렇게까지 대단하지 않기 때문에 생략을 하겠다. 궁금하면 구글에서 더 검색을 해 보자. 

    아무튼 나는 이걸 알고 있음에도 function() 안에서 this를 사용하는 실수를 했던 것이다.


    이 부분은 나도 이유를 알고 있어서 어떻게 할까 고민을 했다.

    1.가장 쉬운 방법으로는 alert 에 번호를 활용하지 않고 그냥 "알람 시각입니다" 따위로 퉁치는 것이다.

    하지만 나는 애초부터 "*번 알람입니다" 보다 더 원대한 목적인 '알람마다 서로 다른 문구 설정할 수 있게 만들기' 를 염두에 두고 만드는 것이기 때문에 일괄적으로 퉁치는 것은 허용 할 수 없었다.


    2. 전역에다가 알람 갯수만큼 메시지 변수를 만들기.

    this 를 쓰지 않고 전역에 ALARM_MESSAGE[n] 같은 변수를 만들어서 쓸 수 있다.

    이것은 구현상에는 문제가 없지만 나에게는 너무 깔끔하지 못한 방법으로 느껴졌다. 알람시계마다 알람 메시지가 배당이 된다면 응당 그 메모리는 알람시계라는 객체 안에서 쓰여야 깔끔해 보인다.

    객체 안에 변수를 넣을 수 없다고 외부에다가 객체 수만큼의 길이를 가진 배열들을 만드는 것은 마치 아파트 앞 마당에다가 각 방의 번호를 적어 놓는 것 같은 찜찜하고 깔끔하지 못한.... 

    아무튼 나는 그런 게 싫다.


    3. 전역에다가 알람 메시지 변수를 하나 만들어서 돌려쓰기.

    내가 쓴 방법이다. 어떤 방법인지 지금부터 보자.


    먼저 전역 공간에다가 var ALARM_MESSAGE; 라고 적어서 알람메시지 변수를 작성했다.

    그리고 ringalarm 함수를 이렇게 만들었다.

    1
    2
    3
    4
    5
      this.ringalarm=function(){
          this.alarmsound.play();
          ALARM_MESSAGE=(this.number+1)+"번 알람입니다.";
          setTimeout(function(){alert(ALARM_MESSAGE);},1000);
      }
    cs

    이렇게 하면 ALARM_MESSAGE 라는 "전역"변수에 알람메시지가 들어가고, 전역이기 때문에 function() 안에서도 마음껏 불러 쓸 수 있다.


    이것의 문제는, 알람 2개 이상이 동시에(1000ms 이내의 간격으로) 울릴 때, 알람 메시지가 바꿔치기 당한다는 것인데...

    이게 무슨 기상 대비용도 아니고 2개 이상의 알람을 같은 시각에 맞춰 놓을리는 없다고 생각하고 배제했다. 동시에 2개의 메시지를 받는 게 대체 무슨 소용이란 말이야?

    애초에 시계 자체도 1초(1000ms) 간격으로밖에 알람을 설정할 수 없게 되어 있다. 과감히 배제해도 될 것이다.



    이렇게 하니까 두 번째 사소한 문제가 발생했는데, 알람 시계가 울릴 때 alert 창이 여러개가 후두둑 뜬다는 점이었다.

    그 이유는 간단한데, 애초에 알람시각이 되었음을 판정하기 위해 런타임용으로 집어넣은 함수가 0.05초마다 한 번씩 작동되고, 알람 시각 판정은 초단위까지만 같으면 알람 시각인 것으로 판정하기 때문에, 1초동안 0.05초마다 알람시각이 된 것으로 판단해서 여러개를 띄우는 것이다.

    이전에는 알람 시각이 되면 alert가 바로 떴기 때문에 페이지 전체가 멈춰버리고, 런타임용 함수도 멈추고, 따라서 그 alert 창을 1초 이내에 끄지 않는 한 뜰 일이 없었다.


    이것도 그냥 단순하게 해결했는데, 알람 시각이 되었음을 한 번 확인하면 바로 알람을 꺼 버리도록(빨간색으로 되도록) 설정했다. 어차피 같은 시각은 24시간 후에나 돌아오는데 24시간마다 같은 시각에 알람을 울리는 용도로 이 페이지를 쓸 리도 없고 한 번 울린 알람을 다시 켜고 싶다면 클릭 한 번이면 되기 때문에 딱히 문제는 없을 것이다. (쿠키 같은 것을 활용하지 않아서 페이지 자체를 껐다 켜면 다 초기화가 되기 때문에, 24시간동안 같은 창을 열어 놓지 않는 한 쓸 수도 없다)

    알람이 꺼져 있을 때는 알람 시각이어도 울리거나 alert 를 띄우거나 하지 않기 때문에 결과적으로 alert 창은 한 번만 뜬다.

    1
    2
    3
    4
    5
    6
      this.ringalarm=function(){
          this.alarm_enabled=false;
          this.alarmsound.play();
          ALARM_MESSAGE=(this.number+1)+"번 알람입니다.";
          setTimeout(function(){alert(ALARM_MESSAGE);},1000);
      }
    cs

    그래서 만들어진 함수는 이러하다.

    그리고 이것은 아주 잘 작동했다.


    그리고 덤으로, 알람 시각을 설정할 때 (시각을 입력하고 OK버튼을 누를 때) 꺼져 있던 알람도 자동으로 켜지게 만들었다. 

    submitalarm() 함수 안에 this.alarm_enabled=true; 를 추가해줬다.

    이건 뭐.. 코드 한 줄 추가로 사소하게 편의성을 개선했다.




    또 다른 불편한 점이었던 

    "특정 시각에 알람을 울리는 기능만 구현되어 있고, 일정 시간 후에 (즉 1시간 후 알람) 같은 기능이 없다"

    는 부분은 작성해야 할 코드가 좀 더 많을 것 같아서 다음에 해야겠다.

    댓글

Designed by Tistory.