話題のhtmxを試してみた

話題のHTMXを試して見ました。
WebアプリケーションフレームワークはFlaskです。

HTMXを使った場合と使ってない場合を比較したかったので、最初にHTMXを使ってないアプリを作りました。以下はChatGPTさんに「ボタンを押下すると表示された数値がカウントされる、簡単なアプリの例をお願いします。」で生成したコードそのままです。

```python:app.py
from flask import Flask, render_template_string, request, redirect, url_for

app = Flask(__name__)

# カウンターの値を保持する変数
counter = 0

# HTMLテンプレート
html = """
    <!doctype html>
    <html>
    <head><title>Counting App</title></head>
    <body>
        <h1>Counter: {{ counter }}</h1>
        <form action="/count" method="post">
            <input type="submit" value="Count">
        </form>
    </body>
    </html>
"""

@app.route('/')
def index():
    global counter
    return render_template_string(html, counter=counter)

@app.route('/count', methods=['POST'])
def count():
    global counter
    counter += 1  # ボタンが押されるたびにカウンターを増やす
    return redirect(url_for('index'))  # メインページにリダイレクト

if __name__ == '__main__':
    app.run(debug=True)
```

実行すると以下のような画面になります。
![py1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822430/8dfafd13-930e-6d43-a467-0bcdbb5b6b05.png)

ボタンを押下するたびに1加算されます。
![py1a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822430/9a76d39b-7f21-f345-27a5-471f20fbbe02.png)

これだけだと面白くないかなと思い、+2されるボタンを追加して見ました。これはChatGPTさんでなく自分で変更。
```python:app2.py
from flask import Flask, render_template_string, request, redirect, url_for

app = Flask(__name__)

# カウンターの値を保持する変数
counter = 0
counter2 = 0

# HTMLテンプレート
html = """
    <!doctype html>
    <html>
    <head><title>Counting App</title></head>
    <body>
        <h1>Counter: {{ counter }}</h1>
        <form action="/count" method="post">
            <input type="submit" value="Count">
        </form>
    </body>
    <body>
        <h1>Counter2: {{ counter2 }}</h1>
        <form action="/count2" method="post">
            <input type="submit" value="Count2">
        </form>
    </body>
    </html>
"""

@app.route('/')
def index():
    global counter
    global counter2
    return render_template_string(html, counter=counter, counter2=counter2)

@app.route('/count', methods=['POST'])
def count():
    global counter
    counter += 1  # ボタンが押されるたびにカウンターを増やす
    return redirect(url_for('index'))  # メインページにリダイレクト

@app.route('/count2', methods=['POST'])
def count2():
    global counter2
    counter2 += 2  # ボタンが押されるたびにカウンターを増やす
    return redirect(url_for('index'))  # メインページにリダイレクト

if __name__ == '__main__':
    app.run(debug=True)
```
実行すると以下のような画面になります。
![py2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822430/fd69d1f9-cf1e-5253-357e-d61f5a7f7403.png)

上のボタンは押下するたびに1加算されます。下のボタンは押下するたびに2加算されます。
![py2a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822430/a9daa06d-08fd-5bfe-387c-21c3f3f73125.png)


次はHTMXを使ったパターン。ここでまたまたChatGPTさんの登場。「app2.pyですが、HTMX使うとどうなりますか」と聞いて生成したのが下記のコード。

```python:app3.py
from flask import Flask, render_template_string, request, jsonify

app = Flask(__name__)

# カウンターの値を保持する変数
counter = 0
counter2 = 0

# HTMLテンプレート
html = """
<!doctype html>
<html>
<head>
    <title>Counting App</title>
    <script src="https://unpkg.com/htmx.org"></script> <!-- HTMXライブラリを追加 -->
</head>
<body>
    <div id="counter1">
        <h1>Counter: {{ counter }}</h1>
        <form hx-post="/count" hx-target="#counter1" hx-swap="outerHTML">
            <input type="submit" value="Count">
        </form>
    </div>
    <div id="counter2">
        <h1>Counter2: {{ counter2 }}</h1>
        <form hx-post="/count2" hx-target="#counter2" hx-swap="outerHTML">
            <input type="submit" value="Count2">
        </form>
    </div>
</body>
</html>
"""

@app.route('/')
def index():
    global counter, counter2
    return render_template_string(html, counter=counter, counter2=counter2)

@app.route('/count', methods=['POST'])
def count():
    global counter
    counter += 1
    return render_template_string('<div id="counter1"><h1>Counter: {{ counter }}</h1><form hx-post="/count" hx-target="#counter1" hx-swap="outerHTML"><input type="submit" value="Count"></form></div>', counter=counter)

@app.route('/count2', methods=['POST'])
def count2():
    global counter2
    counter2 += 2
    return render_template_string('<div id="counter2"><h1>Counter2: {{ counter2 }}</h1><form hx-post="/count2" hx-target="#counter2" hx-swap="outerHTML"><input type="submit" value="Count2"></form></div>', counter2=counter2)

if __name__ == '__main__':
    app.run(debug=True)
"""

@app.route('/')
def index():
    global counter, counter2
    return render_template_string(html, counter=counter, counter2=counter2)

@app.route('/count', methods=['POST'])
def count():
    global counter
    counter += 1
    return render_template_string('<div id="counter1"><h1>Counter: {{ counter }}</h1><form hx-post="/count" hx-target="#counter1" hx-swap="outerHTML"><input type="submit" value="Count"></form></div>', counter=counter)

@app.route('/count2', methods=['POST'])
def count2():
    global counter2
    counter2 += 2
    return render_template_string('<div id="counter2"><h1>Counter2: {{ counter2 }}</h1><form hx-post="/count2" hx-target="#counter2" hx-swap="outerHTML"><input type="submit" value="Count2"></form></div>', counter2=counter2)

if __name__ == '__main__':
    app.run(debug=True)
```

実行すると以下のような画面になります。

![py3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822430/efff08b4-eaf2-220a-7592-1ae77f070ffc.png)

こちらも上のボタンは押下するたびに1加算されます。下のボタンは押下するたびに2加算されます。見た目はapp2.pyと変わりませんが、ボタンを押下した時にページ全体がリロードされないので画面がチラチラしません。HTMXにした効果がちゃんと出ているようです。

![py3a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822430/0fd76404-8c64-4219-93ce-a5a749d59a84.png)

最後に
実はWebアプリはあまりやったことがなくて、PythonとFlaskも初めて使いました。ChatGPTさんにこんなことやりたいのだけど、どうすりゃいいの? って聞きながら 。