Help us understand the problem. What is going on with this article?

Excelから関数型言語マスター7回目: Vue.js+内部API(更新編)

fukuoka.ex代表のpiacereです
ご覧いただいて、ありがとうございます:bow:

今回は、前回作った、Vue.js+Phoenix内部APIをWeb表示するところから、更にWeb入力をできるようにし、APIによるデータ更新を行います

いわゆるSPA(Single Page Application)としてのデータ操作を今回、実装することになります

このシリーズの、前回までの記事は、以下になります
 |> Excelから関数型言語マスター1回目:データ行の”並べ替え”と”絞り込み”
 |> Excelから関数型言語マスター2回目:データ列の”抽出”、”Web表示”
 |> Excelから関数型言語マスター3回目:WebにDBデータ表示
 |> Excelから関数型言語マスター4回目:Webに外部APIデータ表示
 |> Excelから関数型言語マスター5回目:Webにグラフ表示
 |> Excelから関数型言語マスター6回目:Vue.js+内部API(表示編)

なお、「Phoenix」は、ElixirのWebフレームワークです

内容が、面白かったり、役に立ったら、「いいね」よろしくお願いします :wink:

データ入力可能にし、全件更新できるようにする

まずは、現在のデータ表示から作りやすい、データ更新に対応してみましょう

これまでのデータ表示部分を、入力フィールドに変更し、「全件更新」ボタンを追加した上で、データ更新APIを全データ件数分、繰り返すonUpdateメソッドを追加します

lib/vue_sample_web/templates/page/index.html.eex
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>

<div id="app">

<h1>Posts</h1>
<table border="1">
<tr v-for="result in results">
    <td style="padding: 10px;"><input type="text" v-model="result.title"></td>
    <td style="padding: 10px;"><input type="text" v-model="result.body"></td>
</tr>
</table>
<button v-on:click="onUpdate">全件更新</button>

</div>

<script>
    var app = new Vue
    ( {
        el: '#app',
        data: 
        {
            results: [], 
        }, 
        mounted()
        {
            axios.get( '/posts' )
            .then( response => { this.results = response.data.data } )
        }, 
        methods: 
        {
            onUpdate: async function( evt )
            {
                this.results.forEach( ( result, i ) => 
                {
                    axios.put( '/posts/' + result.id, 
                        { 
                            'post':
                            {
                                'title': result.title, 
                                'body':  result.body
                            } 
                        } )
                } )
            }, 
        }, 
    } )
</script>

「v-model」を使い、入力データをVue.js側に反映

コードの解説ですが、入力フィールドで変更されたデータをVue.js側に反映するには、「v-model」という属性を使います

v-modelの中に書くのは、{{~}}で表示をしたのと同様、Vue.jsの「data:」部で定義したモデルとなります(実際には、v-forでバラされたモデル断片)

なお、v-modelは、HTMLからVue.jsへの一方通行の反映では無く、Vue.jsからHTMLへの反映も兼ねているので、Vue.js側で「data:」部のモデルを書き換えると、画面表示に自動反映されます(双方向バインディングと呼ばれます)

mountedでこれが使われ、初期表示では、APIで取得したデータを画面に反映します

lib/vue_sample_web/templates/page/index.html.eex
<tr v-for="result in results">
    <td style="padding: 10px;"><input type="text" v-model="result.title"></td>
    <td style="padding: 10px;"><input type="text" v-model="result.body"></td>
</tr>

「v-on:click」でクリック時のハンドラ呼出

「全件更新」ボタンは、クリックされたときに、onUpdateを呼び出します

「v-on:click」という属性を使うことで、クリック時のハンドラメソッドを指定できます

他にも、「v-on:change」や「v-on:focus」、「v-on:blur」等、JavaScriptで利用可能なハンドラと同様のものをVue.jsでも利用できます

なお、引数を指定することもでき、引数指定無の場合は、クリックイベントが発生したパーツ(今回だとbutton)のDOMが暗黙の引数として渡されます

lib/vue_sample_web/templates/page/index.html.eex
<button v-on:click="onUpdate">全件更新</button>

「methods:」部にハンドラを定義

onUpdateの中身は、v-modelで更新されたresultsを全件、PUTメソッドで更新APIを呼び出し続ける処理です

results全件を更新に回す部分は、forEachを使うと、JavaScriptでも、関数型っぽく書けます

第1引数に配列の要素(resultの部分)、第2引数に配列の要素の添字(iの部分)が返りますので、それらを使って全件処理を書けます(今回は、要素のみ利用)

PUTメソッドで送信する内容は、axios.putの第1引数であるURLには、更新対象となるデータのidを指定します

第2引数は、更新内容をJavaScriptオブジェクト形式で書きます

lib/vue_sample_web/templates/page/index.html.eex
<script>

        methods: 
        {
            onUpdate: async function( evt )
            {
                this.results.forEach( ( result, i ) => 
                {
                    axios.put( '/posts/' + result.id, 
                        { 
                            'post':
                            {
                                'title': result.title, 
                                'body':  result.body
                            } 
                        } )
                } )
            }, 
        }, 

</script>

画面表示/入力の確認

では、上記ファイルを保存し、以下のような画面がブラウザ表示されることを確認しましょう
image.png

入力フィールドの値を書き換え、「全件更新」ボタンをクリックすると、APIが呼び出され、データが更新されます

ただし、更新がかかっても、ページ遷移等が無いため、画面上は、何も起きなかったように見えるので、更新後、ブラウザをリロードして、更新したデータが維持されていることを確認してください(もし、更新に失敗していたら、リロードすると、入力したデータが元に戻るはずです)

データ削除

データ削除は、各データ毎に行えるようにします

「削除」ボタンは、データのidを引数指定して、onDeleteに渡すことで、該当データを削除できるようにします

onDeleteは、DELETEメソッドで削除APIをid指定付きで呼び出し、その後、データ取得し直します

resultsが更新されると、自動的にv-modelで定義したHTML側が表示更新され、削除されたデータが表示されなくなります

ここで、メソッドに「async」、削除API呼出のaxios.deleteに「await」が付いているのは、削除が完全に終わってから、データ取得するよう、「同期処理」にするためです

ここを同期にせず、「非同期処理」で走らせると、データ削除が完了しない状態で、データ取得を行ってしまう可能性があるため、そうならないよう、同期処理化しています

lib/vue_sample_web/templates/page/index.html.eex
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>

<div id="app">

<h1>Posts</h1>
<table border="1">
<tr v-for="result in results">
    <td style="padding: 10px;"><input type="text" v-model="result.title"></td>
    <td style="padding: 10px;"><input type="text" v-model="result.body"></td>
    <td><button v-on:click="onDelete( result.id )">削除</button></td>
</tr>
</table>
<button v-on:click="onUpdate">全件更新</button>

</div>

<script>
    var app = new Vue
    ( {
        el: '#app',
        data: 
        {
            results: [], 
        }, 
        mounted()
        {
            axios.get( '/posts' )
            .then( response => { this.results = response.data.data } )
        }, 
        methods: 
        {
            onUpdate: function( evt )
            {
                this.results.forEach( ( result, i ) => 
                {
                    axios.put( '/posts/' + result.id, 
                        { 
                            'post':
                            {
                                'title': result.title, 
                                'body':  result.body
                            } 
                        } )
                } )
            }, 
            onDelete: async function( id )
            {
                await axios.delete( '/posts/' + id )

                axios.get( '/posts' )
                .then( response => { this.results = response.data.data } )
            }, 
        }, 
    } )
</script>

上記ファイルを保存すると、以下のような画面がブラウザ表示されます
image.png

データ追加

最後に、データ追加です

「追加」ボタンでonCreateを呼び出しますが、追加用フィールドは、resultsとは別のモデルを用意します

onCreateは、POSTメソッドで追加APIを呼び出し、その後、データ取得し直します(ここも同期処理化しています)

lib/vue_sample_web/templates/page/index.html.eex
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>

<div id="app">

<h1>Posts</h1>
<table border="1">
<tr v-for="result in results">
    <td style="padding: 10px;"><input type="text" v-model="result.title"></td>
    <td style="padding: 10px;"><input type="text" v-model="result.body"></td>
    <td><button v-on:click="onDelete( result.id )">削除</button></td>
</tr>
<tr>
    <td style="padding: 10px;"><input type="text" v-model="new_title"></td>
    <td style="padding: 10px;"><input type="text" v-model="new_body"></td>
    <td><button v-on:click="onCreate">追加</button></td>
</tr>
</table>
<button v-on:click="onUpdate">全件更新</button>

</div>

<script>
    var app = new Vue
    ( {
        el: '#app',
        data: 
        {
            results:   [], 
            new_title: '', 
            new_body:  '', 
        }, 
        mounted()
        {
            axios.get( '/posts' )
            .then( response => { this.results = response.data.data } )
        }, 
        methods: 
        {
            onUpdate: function( evt )
            {
                this.results.forEach( ( result, i ) => 
                {
                    axios.put( '/posts/' + result.id, 
                        { 
                            'post':
                            {
                                'title': result.title, 
                                'body':  result.body, 
                            } 
                        } )
                } )
            }, 
            onDelete: async function( id )
            {
                await axios.delete( '/posts/' + id )

                axios.get( '/posts' )
                .then( response => { this.results = response.data.data } )
            }, 
            onCreate: async function( evt )
            {
                await axios.post( '/posts/', 
                    { 
                        'post':
                        {
                            'title': this.new_title, 
                            'body':  this.new_body, 
                        } 
                    } )

                this.new_title = ''
                this.new_body  = ''

                axios.get( '/posts' )
                .then( response => { this.results = response.data.data } )
            }, 
        }, 
    } )
</script>

上記ファイルを保存すると、以下のような画面がブラウザ表示されます
image.png

さて、これで、データの追加/更新/削除ができるようになるので、色々遊んでみてください

終わり

今回は、Web入力をできるようにし、Vue.js+内部APIによるデータ更新/削除/追加を実装しました

この内容は、Vue.js+REST APIによるSPA(Single Page Application)データ操作の基本になりますので、覚えておくと、モダンWebアプリ開発の強力な武器になります

さて、ここまでで、Elixir/Phoenix+Vue.jsによるSPA開発の基本は終わりです

次回は、Phoenixアプリを「Gigalixir」というPaaSにリリースして、公開するためのやり方を解説したいと思います

p.s.「いいね」よろしくお願いします

ページ左上の image.pngimage.png のクリックを、どうぞよろしくお願いします:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください!:tada:

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away