2015년 1월 3일 토요일

[node 웹페이지 만들기] 2.node+express+ejs -1


  • nodejs에 대한 설명은 길기도 하고 여러가지가 있지만 위키백과_nodejs 로 대체하겠습니다. 아주 간략히 설명하자면, 자바스크립트로 작성하는 서버사이드 플랫폼입니다.
  • 기본적으로 설치에 대해서는 http://nodejs.org/에서 각자에게 맞는 운영체제에 따라 설치만 해주면됩니다. 이 외의 모든 라이브러리들은 npm을 통하여 설치,사용 할수 있습니다. 
    • npm(Node Packaged Modules)은 Node.js로 만들어진 모듈(라이브러리)을 인터넷을 통하여 쉽게 설치해주는 패키지 모듈입니다.
    • npm install [모듈] 이라는 명령어로 손쉽게 설치 가능합니다.
  • Nodejs에는 수많은 모듈들이 있으며 https://www.npmjs.com/ 에서 확인할 수 있습니다.
  • 수많은 모듈중에 Express는 웹 프레임워크로서 가장 사랑받는 모듈이라고 합니다. 그래서 저 또한 Express를 통하여 웹서버를 구축해보려고 합니다.

Express 설치


원하는 디렉터리에서 
1
npm install express
명령어 하나면 끝납니다.

Express 예제


  • https://www.npmjs.com/package/express 에 나와 있는 예제입니다.


1
2
3
4
5
6
7
8
var express = require('express')
var app = express()

app.get('/', function (req, res) {
  res.send('Hello World')
})

app.listen(3000)


  • 위의 내용을 js파일로 Express 설치한 디렉터리에 만들어 줍니다.(저의경우 sample.js)
  •  '/'의 주소+3000포트로 Hello World로 뿌려주게 됩니다.
  • 그리고 cmd에서 node 명령어를 이용하여 실행시켜줍니다.



1
node sample.js

  • 실행하면 아무런것도 보이지 않는게 정상이고 실행중입니다. Ctrl+C를 눌러 정지시킬수 있습니다.
  • 실행을 확인하기 위해서 127.0.0.1:3000/ 으로 접속하게 되면

  • 의 화면을 볼수 있습니다.
  • 이로서 Expreess의 설치 및 실행을 해 보았습니다.

 Express template

  • 위에서 이미 'Hello World'를 띄우는데성공을 하였더라도 위와같은 방법으로 웹을 구축하려면은 엄청난 작업이 될것입니다.
  • 템플릿을 이용하면 손쉽게 html 등을 동적으로 만들어 줄수 있습니다.
  • html을 손쉽게 만들어주는 템플릿 엔진으로 대표적인 jade와 ejs 두 가지가 있습니다. 저는 ejs를 이용하여 만들어보겠습니다.
  • express-generator를 설치해 줍니다. -g는 전역모듈로서 사용하겠다는 명령어입니다. 역시 cmd에서 작성합니다.


1
npm install express-generator -g

  • 이번에도 한줄만

  • C:\Users\[계정명]\AppData\Roaming\npm\node_modules\express-generator 에서 expreess-generator가 설치됬음을 볼 수 있습니다.
  • expreess-generator를 설치 해 줬으므로 이제 EJS 뼈대를 생성해 줄 차례입니다.

EJS 설치

  • 이제 cmd에서 작업중이던 EJS를 다룰 폴더를 생성하고 뼈대를 생성해 줍니다.
  • 아래 부분에 install dependencdies 라고 출력된 부분을 볼수 있습니다.
  • 의존성 설치를 해야 한다는 것인데 자동으로 생성된 create 두번째 줄의 package.json을 통하여 의존 모듈을 정의하고 있습니다. ejs를 사용하는데 필요한 모듈과 버전을 json표기법으로 명시한것입니다. 이것들을 npm install 이란 명령어를 통하여 자동으로 설치, 관리합니다.
  • 아래는 package.json의 내용입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  "name": "EJS",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "express": "~4.9.0",
    "body-parser": "~1.8.1",
    "cookie-parser": "~1.3.3",
    "morgan": "~1.3.0",
    "serve-favicon": "~2.1.3",
    "debug": "~2.0.0",
    "ejs": "~0.8.5"
  }
}

  • ejs 시작은 node bin/www로 한다는 것을 알수 있고 private를 보아 독립?되어 있다는 것을 알수 있습니다.
  • dependencies 의 내용을 보아 필요한 모듈과 호환되는 버전을 알 수 있습니다.
  • 위에 설명한거와 같이 npm install로 의존성을 설정합시다.


 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
C:\web\EJS>npm install
ejs@0.8.8 node_modules\ejs

cookie-parser@1.3.3 node_modules\cookie-parser
├── cookie-signature@1.0.5
└── cookie@0.1.2

debug@2.0.0 node_modules\debug
└── ms@0.6.2

morgan@1.3.2 node_modules\morgan
├── basic-auth@1.0.0
├── depd@0.4.5
└── on-finished@2.1.0 (ee-first@1.0.5)

serve-favicon@2.1.7 node_modules\serve-favicon
├── fresh@0.2.4
├── ms@0.6.2
└── etag@1.5.1 (crc@3.2.1)

express@4.9.8 node_modules\express
├── fresh@0.2.4
├── cookie@0.1.2
├── merge-descriptors@0.0.2
├── cookie-signature@1.0.5
├── utils-merge@1.0.0
├── escape-html@1.0.1
├── range-parser@1.0.2
├── vary@1.0.0
├── methods@1.1.0
├── serve-static@1.6.4
├── finalhandler@0.2.0
├── parseurl@1.3.0
├── media-typer@0.3.0
├── path-to-regexp@0.1.3
├── depd@0.4.5
├── on-finished@2.1.1 (ee-first@1.1.0)
├── etag@1.4.0 (crc@3.0.0)
├── qs@2.2.4
├── type-is@1.5.5 (mime-types@2.0.7)
├── send@0.9.3 (ms@0.6.2, destroy@1.0.3, mime@1.2.11, on-finished@2.1.0)
├── proxy-addr@1.0.4 (forwarded@0.1.0, ipaddr.js@0.1.5)
└── accepts@1.1.4 (negotiator@0.4.9, mime-types@2.0.7)

body-parser@1.8.4 node_modules\body-parser
├── media-typer@0.3.0
├── bytes@1.0.0
├── raw-body@1.3.0
├── depd@0.4.5
├── on-finished@2.1.0 (ee-first@1.0.5)
├── qs@2.2.4
├── iconv-lite@0.4.4
└── type-is@1.5.5 (mime-types@2.0.7)

C:\web\EJS>


  • 자동으로 package.json에 명시되어 있던 모든 모듈을 자동으로 설치 하였습니다. 참 편리하네요
  • 이로서 EJS 설치가 완료되었습니다.

EJS 뜯어보기


  • 는 다음에..

[node 웹페이지 만들기] 1.html 회원가입페이지 만들기


  • node를 공부하며 간단한 웹서버(회원가입페이지) 만들기를 해보려고 합니다.
  • node를 중심으로 웹페이지를 보여주고, DB에 저장,호출해 보려고 합니다.
  • 먼저 view 부분인 html(이후 ejs)부분입니다.
  • 크게 javascript, html, css 부분으로 나눌수 있습니다.
  • 먼저 html 부분입니다.


 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
<!DOCTYPE html>
<html lang='ko'>
<head>
 <meta charset='utf-8'/>
 <title>boards</title>
 <link rel="stylesheet" type="text/css" href="style/mycss2.css">
 <script type="text/javascript" src="js/jquery-1.9.1.min.js"></script>
 <script type="text/javascript" src="js/myscript2.js"></script>
</head>
<body>
 <header></header>
 <div class = "body">
  <form name="joinfrm" id="joinfrm" action="joinProc.do" method="post">
  <input type="hidden" id="id_ch" name="id_ch" value="false"/>
  <input type="hidden" id="pass_ch" name="pass_ch" value="false"/>
  <input type="hidden" id="email_ch" name="email_ch" value="false"/>
  <input type="hidden" id="m_tel" name="m_tel" value=""/>
  <div class ="wrapper_table">
   <div class="join_header">
    <p>회원 가입</p>
   </div>
   <div class="join_body">
    <dl>
     <div>
      <dt><label>User ID</label></dt>
      <dd><input id="m_id" name="m_id" type="text" size = "30" maxlength="10" name="m_id" id="m_id" placeholder="아이디"></dd>
     </div>
     <div class='arrow_box' id="id_notice">
        <ul>
      <li><a class="notice" id="info_id1">* 영문 또는, 영문과 숫자의 조합으로 설정해주시기 바랍니다. </a></li>
      <li><a class="notice" id="info_id2">* 5자 이상 12자 이하로 설정 가능합니다.</a></li>
        </ul>
       </div>
     <div>
      <dt><label>PassWord</label></dt>
      <dd><input id="m_pass" name="m_pass" type="password" size = "30" maxlegnth="10" placeholder="비밀번호"></dd>
     </div>
    <div class='arrow_box' id="pass_notice">
        <ul>
      <li><a class = "notice" id="info_pass1">* 영문과 숫자의 조합으로 설정해주시기 바랍니다.</li>
      <li><a class = "notice" id="info_pass2">* 8자 이상으로 설정 가능합니다.</a></li>
        </ul>
       </div>
     <dt><label>PassWord Check</label></dt>
     <dd><input id="m_pass_ch" name="m_pass_ch" type="password" size = "30" maxlength="10" placeholder="비밀번호 확인"></dd>
    <div class='arrow_box' id="pass_ch_notice" style="display:none;">
         <ul>
       <li><a class = "notice" style="color:red;">비밀번호를 확인해 주세요.</a></li>
         </ul>
       </div>
     <dt><label>Name</label></dt>
     <dd><input id="m_name" name="m_name" type="text" size = "30" maxlength="10" placeholder="이름"></dd>
     <dt><label>Birth Date</label></dt>
     <dd><input id="m_date" name="m_date" type="date" min="1930-01-01" max='2010-12-31'></dd>
     <dt><label>Phone Number</label></dt>
     <dd class = "tel"><select id = "m_tel1" name ="tel1"><option selected>010</option>
      <option>011</option>
      <option>016</option>
      <option>017</option>
      <option>019</option>
     </select>
     &nbsp-&nbsp<input id = "m_tel2" name ="m_tel2" type="text" minlength="3" maxlength="4" size="4" required/>
     &nbsp-&nbsp<input id = "m_tel3" name ="m_tel3" type="text" maxlength="4" minlength="4" size="4" required/>
     </dd>
     <dt><label>Gender</label></dt>
     <dd><input id="m_gender" name="m_gender" type="radio" value="남" required></input>&nbsp&nbsp<input name="m_gender" id="m_gender" type="radio" value="여"required></input></dd>
     <dt><label>Email</label></dt>
     <dd><input id="m_email" name="m_email" type="text" placeholder="E-mail"/></dd>
    <div class='arrow_box' id="email_notice" style="display:none;">
         <ul>
       <li><a class = "notice" style="color:red;">이메일 형식이 맞지 않습니다.</a></li>
         </ul>
       </div>
    </dl>
   </div>
   <div class="join_footer">
    <input type="button" value="가입" onclick="doSubmit()">
   </div>
  </div>
  </form>

 </div>
</body>
</html>


  •  table로 작성하려다가 여러 사이트의 회원가입 사이트를 구경하다가 '생활코딩'의 회원가입폼 소스를 뜯어보고 참조해서 만들었기 때문에 table이 아닌 div, dt,dd를 이용하여 만들어 봤습니다. 
  • 여기서 중요한 것은 전달되는 값들의 name값입니다. DB의 테이블의 컬럼값과 변수명을 통일하지 않는다면 나중에 node를 통하여 mysql로 값을 넘겨줄 경우 새로 맵핑해줘야 하는 번거로움이 발생합니다.
  • form에서 전달하는 방법은 post로 해줘야 합니다. get으로 전송할 경우 인젝션에 취약해지기 때문입니다. 또한 통상적으로 값을 입력하고 수정,삭제하는 페이지로의 값 전송에 있어서는 post를 사용하고 단순히 값을 보여주는 페이지, 예로들어 게시판 글보기 페이지 같은 경우는 get을 이용하여 그 글에 대한고유 url을 만들어 줄 수 있기 때문에 get을 사용한다고 합니다.
  • 단순한 html은 아무런 기능은 없으니 바로 css로 넘어가겠습니다.



  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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/* http://meyerweb.com/eric/tools/css/reset/
   v2.0 | 20110126
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
    display: block;
}
body {
    line-height: 1;
}
ol, ul {
    list-style: none;
}
blockquote, q {
    quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
    content: '';
    content: none;
}
table {
    border-collapse: collapse;
    border-spacing: 0;
}
/*초기화 끝 css 시작*/
.body{
 width: 1000px;
 margin: 0 auto;
}

table .label{
 font-size: 1em;
 color : #aabbcc;
}
table .notice{
 color: blue;
 font-size: 0.7em;
 display : none;
}

.wrapper_table{
 margin: 50px auto;
 box-shadow :0 0 10px #bbb;
 /*border: 1px solid blue;*/
 height: auto;
 text-align: center;
 width: 500px;

}
/*join_header*/
.wrapper_table .join_header{
 background-color: #77b9cb;
 border-bottom: 1px solid #bbb;
 height: 50px;
}

.join_header p{
 float: left;
 font-size: 1.2em;
 font-weight: bold;
 padding: 13px 0px 30px 13px;
}
/*join_body*/
.wrapper_table .join_body{
 border-bottom: 1px solid #bbb;
 height: auto;
 text-align: center;
 padding: 0px 50px 20px 50px;
}

.join_body dt{
 margin-top: 20px;
 height: 20px;
 text-align: left;
}

.join_body dt label{
 font-size: 0.8em;
 color: #aaa;
}

.join_body dd input{
 width: 360px;
 height: 40px;
 padding: 0 20px;
 font-size: 20px;
}

.join_body dd select{
 font-size: 20px;
 width: 115px;
 height: 40px;
}

#m_tel2, #m_tel3{
 width: 110px;
 height: 36px;
 padding : 0;
}

#m_gender{
 width: 100px;
}

/*join_footer*/
.wrapper_table .join_footer{
 height: 50px;
 text-align: center;
}

.join_footer input{
 border: 0px;
 background: #3c9;
 width: 400px;
 height: 40px;
 margin: 5px;
 font-size: 20px;
 color: #ddd;
}

.arrow_box {
 margin-top:20px;
 position: relative;
 padding: 5px;
 background: #88b7d5;
 border: 4px solid #c2e1f5;
 text-align: left;
}
.arrow_box:after, .arrow_box:before {
 bottom: 100%;
 left: 50%;
 border: solid transparent;
 content: " ";
 height: 0;
 width: 0;
 position: absolute;
 pointer-events: none;
}

.arrow_box:after {
 border-color: rgba(136, 183, 213, 0);
 border-bottom-color: #88b7d5;
 border-width: 10px;
 margin-left: -180px;
}
.arrow_box:before {
 border-color: rgba(194, 225, 245, 0);
 border-bottom-color: #c2e1f5;
 border-width: 16px;
 margin-left: -186px;
}

.arrow_box a{
  font-size:0.7em;
}


  • 1~49번 라인은 css 초기화를 위한 문자으로 제일 처음 적어주게 됩니다.
  • css 초기화를 하는 이유는 브라우저마다 padding, margin등의 기본값이 초금씩 다르기 때문입니다. 즉 모든 브라우저에서 디자인으로 보여질수 있도록 하기 위해서 입니다.
  • 다음은 마지막 부분인 Javascript 부분,




  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
function atrim(str){
  return str.replace(/\s/g,'');
}
function blankcheck(el,msg){
 if(el.value==""){
  alert(msg+"를(을) 입력해주세요.");
  el.focus();
  return true;
 }
 if(el.value.length != atrim(el.value).length){
  alert(msg+"에는 공백을 허용하지 않습니다.");
  el.focus();
  return true;
 }
}
function doSubmit() {

 console.log("doSubmit");

 if(blankcheck(joinfrm.m_id,'아이디') || joinfrm.id_ch.value == false) {
  return;
 }

 if(blankcheck(joinfrm.m_pass,'비밀번호') || joinfrm.pass_ch.value == false) {
  return;
 }

 if(blankcheck(joinfrm.m_pass_ch,'비밀번호'))
  return;

 if(blankcheck(joinfrm.m_name,'이름'))
  return;

 if(blankcheck(joinfrm.m_date,'날자'))
  return;

 if(blankcheck(joinfrm.m_tel1,'전화'))
  return;
 if(blankcheck(joinfrm.m_tel2,'전화'))
  return;
 if(blankcheck(joinfrm.m_tel3,'전화'))
  return;
 joinfrm.m_tel.value = joinfrm.m_tel1.value+"-"+joinfrm.m_tel2.value+"-"+joinfrm.m_tel3.value;
 if(blankcheck(joinfrm.m_gender,'성별'))
  return;

 if(blankcheck(joinfrm.m_email,'이메일' || joinfrm.email_ch.value == false))
  return;
 joinfrm.submit();
}

$(document).ready(function() {
 var check_Eng= /[a-z]|[A-Z]/;
 var check_Num= /[0-9]/;
 var check_Num_Eng= /[0-9]|[a-z]|[A-Z]/;
 var check_kor = /([^가-힣ㄱ-ㅎㅏ-ㅣ\x20])/i;
 var check_email = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;
 $("#m_id").change(function(){
  joinfrm.id_ch.value = false; //id_ch 히든값 초기화 후 검사 시작
  console.log('1. '+joinfrm.id_ch.value);
  // alert('id값 변경');
  var id = joinfrm.m_id.value;
  var id_flag = false;
  if(id.match(check_Num_Eng) == null || id.match(check_Eng) == null){
  /*if(!check_kor.test(id)){*/
   $("#info_id1").css("color","red");
   // alert('영문 또는 영문,숫자 조합을 사용하여야 합니다.');
   id_flag = true;
  }else{$("#info_id1").css("color","blue");}

  if(id.length<5 || id.length >13){
   $("#info_id2").css("color","red");
   id_flag = true;
  }else{$("#info_id2").css("color","blue");}
  if(id_flag){
   joinfrm.m_id.focus();
   return;
  }
  joinfrm.id_ch.value = true;
  console.log('2. '+joinfrm.id_ch.value);
 });

 $("#m_pass").change(function(){
  joinfrm.pass_ch.value = false;
  // joinfrm.m_pass_ch.value = ""; //비밀번호 변경시 비밀번호 확인 칸 초기화
  var pass = joinfrm.m_pass.value;
  // alert("포커스 아웃  " + pass);
  var pass_flag = false;
  if(pass.match(check_Num_Eng) == null || pass.match(check_Num) == null || pass.match(check_Eng) == null){
   // alert("패스워드 잘못됨");
   $("#info_pass1").css("color","red");
   pass_flag = true;
  }else{$("#info_pass1").css("color","blue");}
  if(pass.length<8){
   $("#info_pass2").css("color","red");
   pass_flag = true;
  }else{$("#info_pass2").css("color","blue");}
  if(pass_flag){
   joinfrm.m_pass.focus();
   return;
  }
  joinfrm.pass_ch.value = true;
  console.log(pass);

 });

 $("#m_pass_ch").change(function() {
   alert("ㅎㅇ");
  joinfrm.pass_ch.value = false;
  if(joinfrm.m_pass_ch.value != joinfrm.m_pass.value){
   alert("ㅎㅇ");
   $("#pass_ch_notice").css("display","block");
   joinfrm.m_pass_ch.focus();
   return;
  }
  $("#pass_ch_notice").css("display","none");
  joinfrm.pass_ch.value = true;
 });

 $("#m_email").change(function(){
  joinfrm.email_ch.value = false;
  if(!joinfrm.m_email.value.match(check_email)){
   $("#email_notice").css("display","block");
   joinfrm.m_email.focus();
   return;
  }
  $("#email_notice").css("display","none");
  joinfrm.email_ch.value = true;
 });
});


  • 1~3번 Line : 공백에 대한 유효성검사를 위해서 만들어 봤습니다. 변수의 공백을 없애고 그 값을 반환합니다.
  • 4~10번 Line : 빈칸에 대한 검사를 하고 atrim함수를 호출하여 공백에대한 유효성 검사도 같이합니다.
  • 16~50번 Line : html 아래 부분의 버튼을 눌렀을 경우 호출하며 위의 유효성 검사 함수를 호출하여 실행합니다. 
  • 52번~ : 아이디, 비밀번호, 이메일에 대한 유효성 검사를 위해서 jquery의 change함수를 이용하여 구현해봤습니다. 이 부분에 대한 유효성은 정규표현식을 이용하였습니다.
완성된 페이지는
이렇습니다.
아이디와 비밀번호를 잘못입력하게 되면 해당되는 경고문구가 빨간색으로 칠해지게 됩니다.
비밀번호와 다시 입력하여 확인하는 경우, 이메일이 형식에 맞지않는경우 해당칸 아래에 경고문구가 생기게 됩니다.