Thymeleaf
vue.js
Vue.js #3Day 19

Thymeleaf + jQuery + Vue.js ここからはじめる(?)新しいフロント開発

0.はじめに(長い前置き)

Vue.jsの使い方そのものは公式ドキュメントが日本語で充実しているし、使い始めは公式のチュートリアルに沿っていけばいいので何書くかなーってことで
バックエンドを中心にやってきた人に向けて、SpringBootで作ったアプリにVueをちょこっと導入してみたらこんな感じ!というコードを書いてみました!

イマドキのフロント開発と行ったらNode入れてnpm入れてJavaScriptもES2015で書いてWebPackでコンパイルしてbabelでトランスパイルしてってちょっとまってまって無理無理無理!
てな感じで、まあ俺にはjQueryがお似合いさと世間の人が「jQueryはさすがにもう辛い」と言ってるのがピンとこないままjQueryを使い続けてた過去の自分に送るアドベント記事です。
(訳:異論は受け付けません)

というわけで

明らかにベストプラクティスではないのですが、
こんなところからでも始められるよ、というのと、
jQueryで書いたときの違いがわかっていただけるのではないかなと思って書きました。

このコードのHTML、JavaScriptはコンパイルもトランスパイルもいらないです。
NodeもWebPackもBabelも使ってないです。(ついでにgulpも)。
怖くないですよ!

1. HTML(とpom.xml)

とりあえず先にHTMLを全文。解説は後でします。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <script th:src="@{/webjars/vue/2.5.3-1/vue.js}"></script>
    <script th:src="@{/webjars/jquery/1.11.1/jquery.min.js}"></script>
</head>
<body>
    some contents<br />

    <label th:text="${thymeleaf_value}"></label>

    <div id="content">
        <div v-for="d in datalist" v-bind:style="{backgroundColor:d.color}">
            {{d.id}}
            <input type="text" v-model="d.color"/>

        </div>
        <button v-on:click="addList">Add List</button><br />
        <button v-on:click="postlist">POST!</button><br />
        <button v-on:click="getlist">GET!</button>
    </div>
    <script th:src="@{/index.js}"></script>
</body>
</html>

v-for,v-on,v-model,v-bindというのがVue.jsのキーワードです。
まあとりあえずおいときましょう。
Vue.js自体もJavaエンジニアにやさしくMavenリポジトリからWebJarで持ってきています。で<script src=....でインポートです。普通ですね。
最後にindex.jsをインポートしています。処理の本体はこっちです。

あと、さりげなく<label th:text="....>でThymeleafの構文を使ってJavaから値を持ってきました。合わせて動きますよ、ということで。

pom.xmlはこんな感じ。

pom.xml
<!-- https://mvnrepository.com/artifact/org.webjars/vue -->
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>vue</artifactId>
    <version>2.5.3-1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.webjars/jquery -->
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>1.11.1</version>
</dependency>

今回はajax処理には敢えてjQueryを使いました。
何もかもを新しくしなくても大丈夫だよ、ということで。

2. JavaScript

index.js
var myVueapp = new Vue({
    el:"#content"
    ,data:{
        datalist:[{id:1, color:"white"}]
    }
    ,methods:{
        // 1行追加
        addList:function(){
            this.datalist.push({id:this.datalist.length+1, color:'white'});
        }
        // POST処理 datalistをそのまま投げる
        ,postlist:function(){
            $.ajax({url:'/post'
                ,type:"post"
                ,data:JSON.stringify(this.datalist)
                ,contentType: 'application/json'});
        }
        // GET処理 受け取ったJSONをそのままdatalistに入れる
        ,getlist:function(){
            var self=this;
            $.get('/get'
            ,null
            ,function(data){self.datalist=data}
            );
        }
    }
})

1行目、Vueのインスタンスを作っています。
で、その中でまずel。これはどのコンテンツをVueで扱うかの指定ですね。
ここでは#contentDIVを対象にしています。

次がdata、これがVueで扱うデータ部。
methods、これがVueのメソッドですね。
とりあえずこんなもんにしておきましょう。

3. 表示

さて、画面を表示してみましょう。
一応、Java側のソースはこんなんです。

@GetMapping("")
public String index(Model model){   
    model.addAttribute("thymeleaf_value", "ValueFromJava!");
    return "index";
}

image.png

はい。表示されました。背景を白くすると画像の境目がわかりにくいですね。ごめんなさい(直さない)

AddListをクリック

AddListをクリックしてみましょう。

image.png

はい、1行増えました!

AddListのボタンに書いているv-on:click="addList"により、クリックイベントが発生するとVueで定義↓addListメソッドが実行されます。

addList:function(){
 this.datalist.push({id:this.datalist.length+1, color:'white'});
}

さて、ここでやっているのはdatalistに1要素追加しているだけです。
1行分のHTMLはいつ書かれたのでしょうか?

index.html(v-for)
<div v-for="d in datalist" v-bind:style="{backgroundColor:d.color}">
  {d.id}}
  <input type="text" v-model="d.color"/>
</div>

v-for="d in datalist"によりdatalistの件数分これが繰り返されるようになっています。Thymeleafでいうところのth:eachですね。
決定的な違いは、datalistの中身が変わったら即座に反映されるという点。
Vueではデータだけ変更すればHTMLは勝手に変わるんですね!
Vueでは、というかこれがイマドキのフロントエンドフレームワークの2-wayバインディングというやつですね。

テキストボックスへ入力

というわけで次はテキストボックスにredと入力してみましょう。

image.png

なんと背景が赤くなりましたね!
<input type="text" v-model="d.color"/>のv-modelの効果でテキストボックスの変更値が即座にcolorに反映されています。
そしてv-bind:style="{backgroundColor:d.color}によりbackground-colorにテキストボックスで入力した値が設定されるわけですね。
jQueryで変更箇所の親要素を探してCSSを追加して・・・とかやらなくていいわけです。

細かいところでいうとテキストボックスやdivにidやnameを指定してないですけど問題なく動いてます。

4. Ajax処理

GETして画面に反映

GET!ボタンを押してみましょう。

image.png

明細が入れ替わりましたね。

getlistでajaxで取得してきたjsonのリストをそのままdatalistに代入しています。
datalistの変更に応じて画面がそのまま変わった、ということですね。

Java側のソースはこんな感じ。

@GetMapping("get")
public List<IdAndColor>  get(){
    List<IdAndColor> list = new ArrayList<IdAndColor>();
    list.add(new IdAndColor(0,"blue"));
    list.add(new IdAndColor(1,"navy"));
    list.add(new IdAndColor(2,"yellow"));   
    return list;
}

POST

今度はPOSTしてみましょう。successfuncを書いていないので画面は何も変わりません。
サーバーサイドで受け取ったものをコンソールに出力しているだけです。

image.png

うまく出力されましたね。
ここで言いたいのは、POSTするために画面の値をかき集めて変換して・・・ということをやらなくてもdatalistをそのまま投げるだけでOKということです!

jQueryでは画面には表示しないんだけどPOSTや判定に使う<index type="hidden">やらオレオレattributeを大量に使っていたんですが、Vueを使うようになってそれらがさっぱりなくなりました。
javaScriptの変数としてだけ持っていればよいのです。素晴らしいですね!

一応、Javaのソースも。

@PostMapping("post")
public String post(@Valid @RequestBody List<IdAndColor> body
        ,BindingResult bindingResult){
    if(bindingResult.hasErrors()){
        return "";
    }
    body.forEach(r->{System.out.println("id:"+r.id + ", color:"+r.color);});                
    return "";
}

5. あとがき

いかがでしたでしょうか。
なんとなく、「Vueできそう!」「いいなこれ!」と思っていただけたら嬉しいです。

次にコンポーネントを使うと画面の部品単位でHTML、Script、CSSをまとめて管理できるようになったり、こうするとnode,npm,webpackを入れてコンパイルしないといけなくなったり、それやりだすと親子関係のデータ管理が複雑になってくるのでVuexを入れてデータストアにしてみたりとどんどん深みにはまってはいくのですが、やっていてとても楽しいです。

何よりサーバーサイドとクライアントサイドがスッパリわかれて非常にわかりやすくなりました。テストもやりやすいですよね。

来年はどんな年になるかな〜?

今回のソース

https://github.com/syukai/VuejsWithThymeleaf