Ajaxチャット / 投票(原理 : 星マーク) / タスク登録 / 画像upload/ モーダル表示


### チャット

```ruby:python
from flask import Flask, render_template, request, jsonify
import time

app = Flask(__name__)

messages = []

@app.route('/')
def index():
    return render_template('chat.html')

@app.route('/send', methods=['POST'])
def send_message():
    message = request.form['message']
    messages.append(message)
    return jsonify({'message': message})

@app.route('/get_messages', methods=['GET'])
def get_messages():
    return jsonify({'messages': messages})
```

```ruby:html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>リアルタイムチャット</title>
    <script>
        function sendMessage() {
            const messageText = document.getElementById('message').value;
            if (messageText) {
                fetch('/send', {
                    method: 'POST',
                    body: new FormData(document.getElementById('messageForm'))
                })
                .then(response => response.json())
                .then(data => {
                    const message = data.message;
                    const newMessage = document.createElement('div');
                    newMessage.textContent = message;
                    document.getElementById('chatBox').appendChild(newMessage);
                    document.getElementById('message').value = '';  // フォームをリセット
                });
            }
        }

        function loadMessages() {
            fetch('/get_messages')
                .then(response => response.json())
                .then(data => {
                    const chatBox = document.getElementById('chatBox');
                    chatBox.innerHTML = '';
                    data.messages.forEach(message => {
                        const newMessage = document.createElement('div');
                        newMessage.textContent = message;
                        chatBox.appendChild(newMessage);
                    });
                });
        }

        setInterval(loadMessages, 2000);  // 2秒ごとに新しいメッセージを取得
    </script>
</head>
<body>

<h1>リアルタイムチャット</h1>

<div id="chatBox"></div>
<form id="messageForm" onsubmit="event.preventDefault(); sendMessage();">
    <textarea id="message" rows="4" cols="50"></textarea><br>
    <button type="submit">送信</button>
</form>

</body>
</html>
```

### 投票

```ruby:python
from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

votes = {'option1': 0, 'option2': 0, 'option3': 0}

@app.route('/')
def index():
    return render_template('vote.html', votes=votes)

@app.route('/vote', methods=['POST'])
def vote():
    option = request.form['option']
    if option in votes:
        votes[option] += 1
    return jsonify(votes)
```

```ruby:html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>投票システム</title>
    <script>
        function submitVote(option) {
            fetch('/vote', {
                method: 'POST',
                body: new URLSearchParams({'option': option})
            })
            .then(response => response.json())
            .then(data => {
                document.getElementById('voteResult').innerHTML = `
                    Option 1: ${data.option1} votes<br>
                    Option 2: ${data.option2} votes<br>
                    Option 3: ${data.option3} votes`;
            });
        }
    </script>
</head>
<body>

<h1>投票システム</h1>
<button onclick="submitVote('option1')">Option 1</button>
<button onclick="submitVote('option2')">Option 2</button>
<button onclick="submitVote('option3')">Option 3</button>

<h2 id="voteResult">
    Option 1: 0 votes<br>
    Option 2: 0 votes<br>
    Option 3: 0 votes
</h2>

</body>
</html>
```
### 画像アップロード

```ruby:python
from flask import Flask, request, render_template, jsonify
import os

app = Flask(__name__)

UPLOAD_FOLDER = 'uploads/'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

@app.route("/mypage", methods=["GET", "POST"])
def mypage():
    if request.method == "POST":
        name = request.form["name"].strip()
        address = request.form["address"].strip()
        birthdate = request.form["birthdate"].strip()
        likes = request.form["likes"].strip()
        dislikes = request.form["dislikes"].strip()
        image_filename = request.form["image_filename"].strip()
        if name and address:
            db.session.add(Profile(name=name, address=address, birthdate=birthdate, likes=likes, dislikes=dislikes, image_filename=image_filename))
            db.session.commit()
        return redirect("/mypage")
    image_list = os.listdir(app.config['UPLOAD_FOLDER']) if os.path.exists(app.config['UPLOAD_FOLDER']) else []
    return render_template('mypage.html', username=current_user.username, image_list=image_list)

    
@app.route("/upload", methods=["POST"])
@login_required
def upload():
    if 'file' not in request.files:
        return jsonify({'error': 'ファイルが見つかりません'})
    file = request.files.get('file')
    if file: 
        filename = secure_filename(file.filename)
        file_path = os.path.join(upload_folder, filename)
        file.save(file_path)
        return jsonify({'filename': file.filename})
    else:
        return jsonify({'error': 'ファイルめよら'}), 400

@app.route('/uploads/<filename>')
@login_required
def uploaded_file(filename):
    return send_from_directory(app.config('UPLOAD_FOLDER'), filename)
```

```ruby:html
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>mypage</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</head>

<body class="bg-light">

    <!-- モーダル起動ボタン -->
    <button type="button" class="btn btn-primary mt-4" data-bs-toggle="modal" data-bs-target="#uploadModal">
        画像をアップロードする
    </button>

    <!-- モーダル -->
    <div class="modal fade" id="uploadModal" tabindex="-1" aria-labelledby="uploadModalLabel" aria-hidden="true">
      <div class="modal-dialog">
        <div class="modal-content">
          <form id="uploadForm" enctype="multipart/form-data" onsubmit="event.preventDefault(); uploadImage();">
            <div class="modal-header">
              <h5 class="modal-title" id="uploadModalLabel">画像アップロード</h5>
              <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="閉じる"></button>
            </div>
            <div class="modal-body">
              <input type="file" name="file" class="form-control" onchange="previewImage(this)">
              <div id="preview" class="mt-2"></div>
            </div>
            <div class="modal-footer">
              <button type="submit" class="btn btn-success">アップロード</button>
            </div>
          </form>
        </div>
      </div>
    </div>

    <!-- アップロードされた画像表示エリア -->
    <h4 class="mt-5">アップロード画像一覧</h4>
    <div id="imageDisplay" class="d-flex flex-wrap gap-3 mt-2"></div>

    <a href="{{ url_for('main.logout') }}" class="btn btn-danger mt-4">ログアウト</a>
</div>

<script>
function uploadImage() {
    const formData = new FormData(document.getElementById('uploadForm'));
    fetch('/upload', {
        method: 'POST',
        body: formData
    })
    .then(res => res.json())
    .then(data => {
        if (data.filename) {
            const img = document.createElement('img');
            img.src = '/uploads/' + data.filename;
            img.width = 200;
            img.classList.add("img-thumbnail");
            document.getElementById('imageDisplay').appendChild(img);

            // モーダルを閉じる
            const modal = bootstrap.Modal.getInstance(document.getElementById('uploadModal'));
            modal.hide();

            // プレビューも消す
            document.getElementById('preview').innerHTML = '';
        } else {
            alert(data.error || "アップロード失敗");
        }
    });
}

// プレビュー機能
function previewImage(input) {
    const preview = document.getElementById('preview');
    preview.innerHTML = '';
    if (input.files && input.files[0]) {
        const reader = new FileReader();
        reader.onload = function(e) {
            const img = document.createElement('img');
            img.src = e.target.result;
            img.width = 200;
            img.classList.add("img-preview", "img-thumbnail");
            preview.appendChild(img);
        }
        reader.readAsDataURL(input.files[0]);
    }
}
</script>
```

### タスク追加 / 削除

```ruby:python
from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

tasks = []

@app.route('/')
def index():
    return render_template('tasks.html', tasks=tasks)

@app.route('/add-task', methods=['POST'])
def add_task():
    task = request.form['task']
    tasks.append(task)
    return jsonify({'task': task})

@app.route('/delete-task', methods=['POST'])
def delete_task():
    task = request.form['task']
    tasks.remove(task)
    return jsonify({'task': task})
```

```ruby:html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>タスク管理</title>
    <script>
        function addTask() {
            const taskText = document.getElementById('task').value;
            if (taskText) {
                fetch('/add-task', {
                    method: 'POST',
                    body: new URLSearchParams({'task': taskText})
                })
                .then(response => response.json())
                .then(data => {
                    const taskItem = document.createElement('li');
                    taskItem.textContent = data.task;
                    const deleteButton = document.createElement('button');
                    deleteButton.textContent = '削除';
                    deleteButton.onclick = function() {
                        deleteTask(data.task, taskItem);
                    };
                    taskItem.appendChild(deleteButton);
                    document.getElementById('taskList').appendChild(taskItem);
                    document.getElementById('task').value = '';  // フォームをリセット
                });
            }
        }

        function deleteTask(task, taskItem) {
            fetch('/delete-task', {
                method: 'POST',
                body: new URLSearchParams({'task': task})
            })
            .then(() => {
                taskItem.remove();
            });
        }
    </script>
</head>
<body>

<h1>タスク管理</h1>
<input type="text" id="task" placeholder="新しいタスク"><br>
<button onclick="addTask()">タスク追加</button>

<ul id="taskList">
    <!-- タスクはここに表示されます -->
</ul>

</body>
</html>
```

### モーダル表示　やり方

```ruby:
<!-- JS入れる -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>


<!-- モーダル起動ボタン : data-bs-toggle="modal" -->
<button type="button" class="btn btn-primary mt-3" data-bs-toggle="modal" data-bs-target="#uploadModal">
    画像をアップロードする
</button>

<!-- 画像一覧表示 + 画像プレビュー表示 -->
<div class="modal fade" id="uploadModal" tabindex="-1" aria-labelledby="uploadModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <form id="uploadForm" enctype="multipart/form-data" onsubmit="event.preventDefault(); uploadImage();">
        <div class="modal-header">
          <h5 class="modal-title" id="uploadModalLabel">画像アップロード</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="閉じる"></button>
        </div>
        <div class="modal-body">
          <input type="file" name="file" class="form-control">
        </div>
        <div class="modal-footer">
          <button type="submit" class="btn btn-success">アップロード</button>
        </div>
      </form>
    </div>
  </div>
</div>

<!-- モーダル閉じる : bootstrap.Modal.getInstance -->
const modal = bootstrap.Modal.getInstance(document.getElementById('uploadModal'));
            modal.hide();
```

### 画像保存処理 理屈

```ruby:画像カラム保存 + 画像保存ができてないと、保存できない
image_filename = request.form["image_filename"].strip()

def upload():
    if 'file' not in request.files:
        return jsonify({'error': 'ファイルが見つかりません'})
    file = request.files.get('file')
    if file: 
        filename = secure_filename(file.filename)
        file_path = os.path.join(upload_folder, filename)
        return jsonify({'filename': file.filename})
    else:
        return jsonify({'error': 'ファイルめよら'}), 400

    image_list = [p.name for p in Path(upload_folder).glob('*.*')]
    return render_template('mypage.html', username=current_user.username, images=image_list)

@main.route('/upload/<filename>')
@login_required
def uploaded_file(filename):
    return send_from_directory(current_app.config('UPLOAD_FOLDER'), filename)
```

# 非同期 仕組み

```ruby:
  mypage に画像選択ボタン＋モーダルあり
  
  JSでfetch(/upload, { method: "POST" })で 送信
  
  Flask側の /upload ルートが画像保存＋JSON返却
  
  JSがファイル名から、 <img src="/uploads/ファイル名"> をmypageに追加
```

### ファイル表示

```python:
@app.route('/uploads/<filename>')
@login_required
def uploaded_file(filename):
    return send_from_directory(app.config('UPLOAD_FOLDER'), filename)
```

```html:image_listでdata表示
<h4 class="mt-5">アップロード画像一覧</h4>
    <div id="imageDisplay" class="d-flex flex-wrap gap-3 mt-2"></div>
    {% for image in image_list %}
      <img src="{{ url_for('uploaded_file', filename=image) }}" width="200" class="img-thumbnail" alt="{{ image }}">
    {% endfor %}
</div>
```

```ruby:アップロードされた画像のファイル名を取得し、それをリストに追加
image_list = os.listdir(app.config['UPLOAD_FOLDER']) if os.path.exists(app.config['UPLOAD_FOLDER']) else []
```

### 生年月日表示

```ruby:html
<p>生年月日：{{ birthdate.strftime('%Y年%m月%d日') }}</p>
```

```ruby:ajax
<script>
  $('#showBirth').on('click', function () {
    const y = $('#year').val();
    const m = $('#month').val();
    const d = $('#date').val();

    if (y === "0" || m === "0" || d === "0") {
      alert("年月日をすべて選んでください。");
      return;
    }

    const dateStr = y + "年" + m + "月" + d + "日";
    $('#birthResult').text(dateStr);
    
    // モーダル表示
    const modal = new bootstrap.Modal(document.getElementById('birthModal'));
    modal.show();
  });
</script>
```

```ruby:生年月日jsonデータを保存
from flask import Flask, request, jsonify
import json
import os

@app.route('/save_birth', methods=['POST'])
def save_birth():
    data = request.get_json()
    save_path = 'saved_birth.json'

    with open(save_path, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

    return jsonify({"message": "保存しました"})
```

### 画像を送る : POSt

```ruby:
 # お決まり文
 function uploadImage() {
    const formData = new FormData(document.getElementById('uploadForm'));
    fetch('/Flaskに書いてるURLへ', {
        method: 'POST',
        body: formData
    })
    .then(res => res.json())
    .then(data => {

# sample code
function uploadImage() {
    const formData = new FormData(document.getElementById('uploadForm'));
    fetch('/upload', {
        method: 'POST',
        body: formData
    })
    .then(res => res.json())
    .then(data => {
        if (data.filename) {
            const img = document.createElement('img');
            img.src = '/uploads/' + data.filename;
            img.width = 200;
            img.classList.add("img-thumbnail");
            document.getElementById('imageDisplay').appendChild(img);
```

### Ajaxでリダイレクトさせる

```ruby:html
<div id="index">
    {{ include "index.html" }}
</div>
<script>
    const index_url = {{ url_for("main.index")|tojson }}
    const index_div = getElementById("index")
    fetch(index_url)
        .then(response => response.text)
        .then(text => geology_div.innerHTML = text)
</script>
```

### 検索機能 ajax

```ruby:ajax
$(document).on("keyup", "#search", function(e){
      var search = $('#search').val();
      if (search == ''){
        loaddata();
      }else{
        $("#load-table").html('');
        $.ajax({
          url : "searchdata",
          type : "POST",
          data : JSON.stringify({name : search}),
          dataType : "json",
          contentType : "application/json; charset=utf-8",
          success : function(data){
            if (data.status == false){
              $("#load-table").html('<tr><td colspan="6" align="center"><h3>No record found</h3></tr></td>');
            }else{
              $.each(data.data, function(key, value){
                $("#load-table").append('<tr>'+
                    '<td class="center">'+ value.sno +'</td>'+
                    '<td>'+ value.name +'</td>'+
                    '<td>'+ value.city +'</td>'+
                    '<td class="center"><button class="edit-btn" id="edit" data-id="'+ value.sno +'" data-name="'+ value.name +'" data-city="'+ value.city +'">Edit</button></td>'+
                    '<td class="center"><button class="delete-btn" id="delete" data-id="'+ value.sno +'">Delete</button></td>'+
                '</tr>');
              });
            }
          }
        });
      }
    });

  });
```

引用 : https://github.com/wpcodevo/flask-note-taking-api/blob/master/Note%20App.postman_collection.json