앞에서 만들어본 메모앱은 프론트엔드 페이지를 거의 만들지 않고 curl 명령으로 테스트를 진행했었습니다.
여기서 프런트엔드 데이지를 개선 및 추가해서 메모앱을 웹에서 작동이 되도록 해보겠습니다.
프론트엔드 페이지는 HTML, CSS, 자바스크립트로 작성하고, Jinja2 이라 불리는 템플릿에 연동되어 동작할 수 있게 만들어 볼께요
개선 하고 추가할 파일은 templates 폴더 아래에 있는 memos.html 과 index.html 입니다.
1. index.html 작성하기
index.html 은 http://127.0.0.1:5000/과 같이 루트 라우트에서 보여지는 페이지입니다.
이 페이지에서 로그인 및 회원가입 기능을 구현하기 위해 아래와 같이 코드를 변경합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>온라인 메모 앱 v1.0</title>
<style>
body { font-family: Arial, Helvetica, sans-serif; }
.container {
width: 300px;
margin: auto;
border: 1px solid #ddd;
padding: 20px;
}
.form-group {
margin-bottom: 10px;
}
.form-group label, .form-group input {
display: block;
width: 100%;
}
.form-group input {
padding: 5px;
margin-top: 5px;
}
.buttons {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
</style>
</head>
<body>
<h2>나의 메모 앱에 오신것을 환영해요</h2>
<p> 이것은 온라인 메모장 앱입니다.</p>
<form action="/login" method="post">
<div class="form-group">
<label for="username">사용자 이름:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">비밀번호:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="buttons">
<input type="submit" type="submit" value="로그인">
<a href="/signup">회원가입</a>
</div>
</form>
</body>
</html>
2. memos.html 작성하기
또한 memos.html도 아래와 같이 작성합니다.
memos.html 은 로그인 한 후에 자신의 메모 리스트 보여지며, 이를 수정, 삭제하는 것은 물론 새로운 메모를 새로 추가하는 기능을 구현하는 페이지가 되겠습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>내 메모 관리 페이지</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" rel="stylesheet">
<style>
/* CSS 스타일 시작 */
.container {
margin-top: 20px;
max-width: 800px;
}
.card {
margin-bottom: 20px;
border: none;
box-shadow: 0 4px 8px rgba(0,0,0,.1);
background-color: #fff;
}
.card-body {
position: relative;
padding: 10px;
}
.memo-title, .memo-content {
width: 100%;
margin-bottom: 10px;
border: 1px solid #ddd;
background-color: #fff;
padding: 10px;
}
.memo-title {
font-size: 1.1rem;
}
.memo-content {
min-height: 100px;
}
.edit-buttons {
margin: 10px;
text-align: right;
margin-right: 0px;
margin-bottom: 0px;
}
.edit-buttons .btn {
background-color: #f8f9fa;
border: none;
border-radius: 5px;
margin-left: 5px;
padding: 5px 10px;
color: #495056;
transition: all 0.3s ease;
}
.edit-buttons .btn:hover {
background-color: #e2e6ee;
transform: scale(1.1);
}
.edit-buttons .btn-edit {
background-color: #e74c3a;
color: #fff;
}
.edit-buttons .btn-edit:hover {
background-color: #c0392a;
}
.edit-buttons .btn-delete {
background-color: #3498BB;
color: #fff;
}
.edit-buttons .btn-delete:hover {
background-color: #2980BB;
}
.btn-primary {
background-color: #3f4644;
color: #fff;
}
.btn-primary:hover {
background-color: #0056BB;
}
.btn-block {
display: block;
width: 100%
}
.header-bar {
background-color: #ff8066;
padding: 10px 0;
text-align: center;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,.1); /* 그림자 effect */
animation: slideDown 0.5s ease-out;
margin: 10px;
position: relative;
display: flex; /* flexbox layout */
justify-content: center; /* 가로 중앙 정렬 */
align-items: center; /* 세로 중앙 정렬 */
}
.header-item {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.header-item:first-child {
left: 20px;
}
.header-item:last-child {
right: 20px;
}
.username-button, .logout-button {
display: flex;
align-items: center;
}
.username-button i, .logout-button i {
margin-right: 5px;
}
.header-bar h1 {
color: white;
margin: 0;
font-size: 1.3em;
font-weight: bold;
transition: all 0.3s ease-in-out; /* 부드러운 효과 변화 */
}
.header-content {
text-align: center;
}
.user-info {
position: absolute;
top: 10px;
right: 20px;
font-size: 0.9rem;
}
.logout-button {
margin-left: 10px;
}
.btn-sm {
padding: 0.15rem 0.5rem;
font-size: .8rem;
line-height: 1.5;
border-radius: 0.2rem;
}
/* 슬라이드 다운 애니메이션 effect */
@keyframes slideDown {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
</style>
<script>
function createMemo() {
let title = document.getElementById('new-title').value;
let content = document.getElementById('new-content').value;
fetch('/memos/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title: title, content: content })
})
.then(response => response.json())
.then(data => {
console.log(data);
window.location.reload(); // 페이지 새로 고침
})
.catch((error) => {
console.error('Error:', error);
});
}
function toggleEdit(id) {
var titleEl = document.getElementById('title-' + id);
var contentEl = document.getElementById('content-' + id)
var isReadOnly = titleEl.readOnly;
titleEl.readOnly = !isReadOnly;
contentEl.readOnly = !isReadOnly;
if (!isReadOnly) {
updateMemo(id);
}
}
function updateMemo(id) {
var title = document.getElementById('title-' + id).value;
var content = document.getElementById('content-' + id).value;
fetch('/memos/update/' + id, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title: title, content: content})
})
.then(response => response.json())
.then(data => {
console.log(data);
alert('메모가 업데이트되었습니다.');
})
.catch((error) => {
console.error('Error:', error);
})
}
function deleteMemo(id) {
if (!confirm('메모를 정말로 삭제하시겠습니까?')) return;
fetch('/memos/delete/' + id, {
method: 'DELETE',
})
.then(response => response.json())
.then(data => {
console.log(data);
window.location.reload(); // 페이지 새로고침
})
.catch((error) => {
console.error('Error:', error);
})
}
</script>
</head>
<body>
<div class="container">
<!-- 헤더바 추가 -->
<div class="header-bar" >
<div class="header-item">
<a href="#" class="btn btn-sm btn-danger username-button">
<i class="fas fa-user"></i> {{ username }}
</a>
</div>
</div>
<h1>나의 메모</h1>
<div class="header-item">
<a href="/logout" class="btn btn-sm btn-danger logout-button">
<i class="fas fa-sign-out-alt"></i> 로그아웃
</a>
</div>
<div class="card">
<div class="card-body">
<input type="text" id="new-title" placeholder="새 메모 제목" class="form-control memo-title">
<textarea id="new-content" placeholder="내용을 입력하세요" class="form-control memo-content"></textarea>
<button onclick="createMemo()" class="btn btn-primary btn-block">메모 추가</button>
</div>
</div>
{% for memo in memos %}
<div class="card memo">
<div class="card-body">
<input type="text" id="title-{{ memo.id }}" value="{{ memo.title }}" class="form-control memo-title" readonly>
<textarea id="content-{{ memo.id }}" class="form-control memo-content" readonly>{{ memo.content }}</textarea>
<div class="edit-buttons">
<button onclick="toggleEdit({{ memo.id }})" class="btn btn-edit"><i class="fas fa-edit"></i></button>
</div>
<div class="edit-buttons">
<button onclick="deleteMemo({{ memo.id }})" class="btn btn-delete"><i class="fas fa-trash-alt"></i></button>
</div>
</div>
</div>
{% endfor %}
</div>
</body>
</html>
3. 테스트해보기
1) http://127.0.0.1:5000/ 로 접속해서 회원 가입하기
2) http://127.0.0.1:5000/ 로 재접속해서 로그인 하기
3) http://127.0.0.1:5000/memos에 접속해서 글쓰고 수정 삭제하기
4) 동일 페이지에서 수정하기
5) 삭제하기
파란색 휴지통을 클릭하면 다음과 같이 메시지를 띄웁니다.
=> 삭제 완료 되었네요
오늘은 여기까지 입니다.
감사합니다.
'IT' 카테고리의 다른 글
FastAPI - Study 1일차 (Hello, FastAPI) (1) | 2024.12.16 |
---|---|
Flask - 애플리케이션(메모앱) 만들어보기 #7 (0) | 2024.11.24 |
Fedora 리눅스 워크스테이션 버전 설치 및 nVidia 드라이버 설치방법(Fedora KDE Plasma의 문제점 발견) (0) | 2024.11.22 |
Flask - 애플리케이션(메모앱) 만들어보기 #5 (0) | 2024.11.19 |
Flask - 애플리케이션(메모앱) 만들어보기 #4 (1) | 2024.11.17 |