Flaskでflexdatalistを使ってオートコンプリート機能を実装

webアプリケーションのフォームで、予期せぬ値を入力できないようにしたくて、今回は、jQueryのライブラリであるflexdatalistを使って、オートコンプリート機能によって、入力値に制限を加える実装をしてみました。

他にも、バリデーションを厳しくかけたり、フォーム送信後に整形したりすることもありますが、今回は、オートコンプリート！

![demo.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/185039/b226941f-466c-36e6-ac7f-b15e2f1c576b.gif)

## もくじ
- flexdatalistでカンタンなデモアプリを作ってみる
- Flaskのフォームでflexdatalistを使ってみる

flexdatalistでカンタンなデモアプリを作ってみる
===================
Flaskに導入する前に、flexdatalistだけで超簡単なデモを作ってみます。

## flexdatalistって何？
- [flexdatalist 公式ドキュメント](http://projects.sergiodinislopes.pt/flexdatalist/)
- JQueryのライブラリ
- 「mutiple values」を使えば、複数の値をカンマ区切りで送信できる。 (← 今回はこれ使った)

## ライブラリの読み込み
まず、ライブラリの読み込みから丁寧目にやります。
 
- index.htmlを作る
- [公式ドキュメント](http://projects.sergiodinislopes.pt/flexdatalist/)の下部にある「HOW TO INSTALL」をもとに、headの中に必要な情報を記入していきます。
- ドキュメント上部にあるDownloadボタンから、必要なファイルをlocalに置きます。
    - js/libディレクトリを作って、その下にとりあえず置いておきます。(下の方に最終的なディレクトリ構造を載せてあります。)

(久しぶりに、jQueryを触って、いろいろ忘れていたから、じっくりと。)

```index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>flexdatalist Sample</title>
    <script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
    <link href="js/lib/jquery-flexdatalist-2.2.4/jquery.flexdatalist.min.css" rel="stylesheet" type="text/css">
    <script src="js/lib/jquery-flexdatalist-2.2.4/jquery.flexdatalist.min.js"></script>
</head>
<body>
</body>
</html> 
```

## デモアプリ実装

とりあえず、index.htmlのbodyタグの中に、下記をベタ書きしていきます。

- 入力欄
- オートコンプリートで入力させたい文字列リスト
- jsの関数

```index.html
<body>

<input type='text'
       class='flexdatalist'
       data-min-length='1'
       multiple='multiple'
       list='drink_list'
       name='drink_list'>

<datalist id="drinki_list">
    <option value="Late">Late</option>
    <option value="Mocha">Mocha</option>
    <option value="Cappuccino">Cappuccino</option>
    <option value="Blend">Blend</option>
    <option value="Espresso">Espresso</option>
    <option value="Lemonade">Lemonade</option>
    <option value="Chai">Chai</option>
    <option value="Cold Brew">Cold Brew</option>
    <option value="Single Origin Espresso">Single Origin Espresso</option>
    <option value="Gibraltar">Gibraltar</option>
</datalist>

<script>
    $('.flexdatalist').flexdatalist({
     minLength: 1
    });
</script>

</body>
```

ブラウザで開いて、正常に動くか確かめてみましょう。
(WebStormとか、PyCharmを使っていたら、index.htmlを右クリックで、「Open in Browser」でカンタン。)

## ハマったところ
- minLength
    - inputタグの中とscriptのjsの中で指定することができる。
    - だが、反映される優先順位が高いのは、inputタグの中。
    - jsの方で `minLength: 100` とかにしても、inputタグの `data-min-length='1'` が優先され、1文字入力すれば検索をしてくれる。

Flaskのフォームでflexdatalistを使ってみる
========================
上で一通り作ったflexdatalistを、Flaskのフォームに適応させていきます。

## Flaskのフォームを作る
まずは、適当にFlaskでフォームを作ってみます。

```python:app.py
from flask import Flask, render_template, request, redirect
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired

app = Flask(__name__)
app.config["SECRET_KEY"] = 'coffee drinker' #CSRF対策で必要らしい

class Form(FlaskForm):
    drink_list = StringField("drink_list", validators=[])

@app.route("/", methods=["GET", "POST"])
def index():
    form = Form()

    return render_template("index.html", form=form)

@app.route("/submit", methods=["POST"])
def submit():
    form = Form()
    
    if form.validate():
        drink_names = request.form["drink_list"]

        return render_template("result.html", drink_name=drink_names)

    return render_template("index.html", form=form)
 
if __name__ == "__main__":
    app.run(debug=True)
```

## flexdatalistにFlaskを適用してみる
先程作ったindex.htmlをFlask対応させた記述に変更します。
ついでに、htmlファイルにあったjsの記述は、新しくjsファイルを作って、そちらに移動させましょう。

## ディレクトリの移動
まず、htmlファイルやjsファイルは、Flaskで決まっている位置に置きましょう。

ちなみに、ディレクトリ構造はこんな感じにしてあります。

```txt
flask-auto-complete
   ├── app.py
   ├── static
   │   ├── drink_list.json
   │   └── js
   │       ├── lib
   │       │   └── jquery-flexdatalist-2.2.4
   │       │       ├── LICENSE
   │       │       ├── README.md
   │       │       ├── bower.json
   │       │       ├── jquery.flexdatalist.css
   │       │       ├── jquery.flexdatalist.js
   │       │       ├── jquery.flexdatalist.min.css
   │       │       ├── jquery.flexdatalist.min.js
   │       │       └── package.json
   │       └── script.js
   └── templates
       ├── index.html
       └── result.html
```

※ script.js, drink_list.json, result.htmlは後で作る。

それに伴い、ファイル内のファイルパスの指定も `url_for` を使って書き直しましょう。

```index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>flexdatalist Sample</title>
    <script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
    <link href="{{ url_for('static', filename='js/lib/jquery-flexdatalist-2.2.4/jquery.flexdatalist.min.css') }}" rel="stylesheet" type="text/css">
    <script src="{{ url_for('static', filename='js/lib/jquery-flexdatalist-2.2.4/jquery.flexdatalist.min.js') }}"></script>
</head>

<body>
<form method="POST" action="/submit">
    {{ form.hidden_tag() }}
    <input type='text'
           class='flexdatalist'
           data-data="{{ url_for('static', filename='drink_list.json')}}"
           name='drink_list'>
    <input type="submit" value="Send">
</form>
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
```

### jsファイルへ移行

```js:script.js
$('.flexdatalist').flexdatalist({
    minLength: 1,
    searchIn: 'name',
    data: "{{ url_for('static', filename='drink_list.json') }}",
    selectionRequired: true,
    multiple: true
});
```


## drink_listをjsonファイルに切り出す
htmlファイルにべた書きしておくと、単純にダサいですし、量が増えたときに見づらくなるので、切り出して別ファイルに置いておきます。

```drink_list.json
[
    {"name": "Late"},
    {"name": "Mocha"},
    {"name": "Cappuccino"},
    {"name": "Blend"},
    {"name": "Espresso"},
    {"name": "Lemonade"},
    {"name": "Chai"},
    {"name": "Cold Brew"},
    {"name": "Single Origin Espresso"},
    {"name": "Gibraltar"}
]
```

## フォーム送信後のページを作成
せっかくなので、フォームを送信後のページを、入力した値を使って表示させました。

```result.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Result</title>
</head>
<body>
 <h2>{{ drink_name }} 美味しかった！！！</h2>
 </body>
</html>
```

このコマンドをターミナルで打つか、PyCharmで実行して、ブラウザにlocalhost:XXXXで、正しく動くか確認しましょう。

```
$ python app.py
```

終わりに
======================
僕の力不足で、理解しづらい点もあったかもしれませんが、無事、実装できましたか？

[GitHub](https://github.com/kotamatsuoka/flask-auto-complete)に開発の流れでコミットしてるので、是非参考にしてください。
また、編集リクエストもお待ちしています。