2019.07.02

Spring BootでWebアプリケーション開発(3)フロントエンドの開発とAPI連携


この記事では Spring Boot を使って、以下のような簡単な辞書アプリを作成します。

完成図

前回は以下を行いました。

  • Eclipse による環境構築
  • O/R マッパーを利用するための、ドメインクラスの実装
  • O/R マッピング用のインターフェースと XML ファイルの作成

今回は、辞書アプリのVue.jsを使用した、初期画面の開発準備と、サーバサイドとの通信の処理の記述を行います。

準備

Vue.js

フロントエンドの実装は、Vue.js で行います。

Vue.js は、JavaScript のフレームワークの1つで、SPAの開発に適しています。また、構造がシンプルであったり、日本語のドキュメントが充実していることから、他の JavaScript フレームワークに比べて学習コストが低いという特徴があります。

雛形の作成

まずは Vue プロジェクトの雛形を作成します。雛形の作成には、vue-cli をインストールします。

vue-cli は、 Vue.js で手軽に開発を行うためのツールです。ここからは node.js がインストール済みの前提で進めます。( node.js がインストールされていない場合はインストールしてください。)

以下のコマンドで、vue-cli をインストールすることが出来ます。

コンソール
$ npm install -g @vue/cli

インストールが完了したら、実際に雛形の作成を行います。以下のコマンドで、Vue プロジェクトの雛形を作成できます。Vue プロジェクトを作成したいフォルダで実行してください。

コンソール
$ vue create [プロジェクト名]

上記を実行すると、

コンソール
Vue CLI v3.7.0
? Please pick a preset: (Use arrow keys)
> default (babel, eslint)
  Manually select features

と、選択肢が表示されます。基本的には default で良いのですが、今回は、Router を使用したいので Manually を選択してください。

Manually を選択すると、再度、以下のように選択肢が出てくるので、Router にチェックを入れます。( Babel と Linter / formatter には最初からチェックが入っています。)

コンソール
? Please pick a preset: Manually select features
? Check the features needed for your project:
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (
>(*) Router
 ( ) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

上記の通りにチェックを入れると、また選択肢が続くので、以下の通りに進めてください。

コンソール
Vue CLI v3.7.0
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: (Press <space> to select, a to toggle all, <i> to invert selection)Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? (y/N) no

上記まで進めて、Enter を押下すると、Vue プロジェクトの作成が始まるので、しばらく待機しましょう。大体30秒から1分近くかかります。

Vue プロジェクトの作成が完了したら、一度、ローカル環境でプロジェクトを起動してみます。作成されたプロジェクトのフォルダに移動して、以下のコマンドを実行し、ローカルのサーバを起動してください。

コンソール
$ npm run serve

上記の実行が完了したら、http://localhost:8080/ にアクセスしてみてください。以下のような画面が表示されれば成功です。

Vue実行画面

Vue.js でのファイルの役割

vue create コマンドで生成されたファイルの役割を、簡単に説明します。

index.html

Vue プロジェクトで表示される画面の土台となるファイルです。このファイルから main.js を読み込み、App.vue を描画することで SPA を実現します。

main.js

Vue インスタンスを生成し、index.htmlApp.vue を描画します。

App.vue

index.html に直接描画されるコンポーネントです。URL に合わせて読み込まれた各コンポーネントが、このファイル内の <router-view/> に描画されることで、コンテンツが切り替わります。

router.js

ページ遷移の際、どのコンポーネントのデータを描画するかを定義しているファイルです。新しくコンポーネントを作成したら、このファイルに URL とのマッピングを定義すると、App.vue に描画できます。

Home.vue

views フォルダ配下に作成されているファイルで router.js によるルーティングの対象となります。以下の記述により、サイトの閲覧者が "/" にアクセスすると Home.vue で書かれたページを閲覧することが出来ます。

router.js
{
   path: '/',
   name: 'home',
   component: Home
 }

"/" という URL に対して、Home コンポーネントを描画するよう定義しています。

HelloWorld.vue

conponent フォルダ配下に作成されているファイルです。このようなファイルは、コンポーネントと呼ばれ、再利用可能な部品として扱われます。今回は Home.vue 内の以下の記述で呼び出されています。

Home.vue
<HelloWorld msg="Welcome to Your Vue.js App"/>

上記の記述により、Home.vue で、HelloWorld.vue を描画しています。HelloWold.vuetemplate タグ内の {{ msg }} に、msg 属性に設定した文字列がバインドされます。

HelloWold.vue のようなコンポーネントを作成して、組み合わせることで、再利用性の高い設計のアプリケーションを構築できます。

ElementUI

今回、フロントエンドの開発に、ElementUI というコンポーネントライブラリを使用します。

ElementUI は Vue.js のコンポーネントライブラリで、簡単に見た目を整えることが出来ます。繰り返し文なども用意されているので、API から取得したレコードを表示する場合にも便利です。

また、アニメーションや、おしゃれなフォームを作成することも出来ます。今回は使用しないのですが、興味があれば調べてみてください。

ElementUIの準備

では、ElementUI を使用するための準備をしていきます。まずはコンソールで、以下のコマンドを実行し ElementUI のインストールを行ってください。

ElementUIのインストール
$ npm install --save element-ui

ElementUIのインストールが完了したら、Vue プロジェクト内で使用できるよう、記述を行います。 main.jsに以下の記述を追加してください。

main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'

import ElementUI from 'element-ui' //追加
import locale from 'element-ui/lib/locale/lang/ja' //追加
import 'element-ui/lib/theme-chalk/index.css' //追加

Vue.config.productionTip = false
Vue.use(ElementUI, {locale}) //追加

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

上記の記述を行うことで、Vue ファイル内で ElementUI のスタイルなどを適用するための <el>タグを使用することが出来るようになります。

メインロジックの実装

ElementUI を使用する準備が整ったところで、実際に Vue.jsを使用して、フロントエンドの実装を行います。フロントエンドでは、以下の機能を実装します。

  • 指定した URL にリクエストが送信された際に、サーバサイドから必要なレコードを取得して、初期画面を表示する
  • 検索窓から分野を指定された際に、サーバサイドから指定された分野に紐付くレコードを取得して、表示する

初期表示画面

初期表示画面では、サーバサイドに Ajax リクエストを行い、DB からすべての辞書データを取得して、以下のように画面に出力します。

Vue実行画面

辞書ページ用の Vue ファイルを作成します。前項で作成した Vue プロジェクトの views フォルダ配下に、Dictionary.vue というファイルを作成します。

サーバサイドからデータを取得する前に、まずはルーティングの設定と表示を確認しましょう。作成したファイルに、とりあえず以下の記述を行います。

Dictionary.vue
<template>        //表示
  <div>
    <el-row type="flex" justify="center">
      <el-col :span="22">
        <div>
          <span>辞書</span>
        </div>
      </el-col>
    <el-row>
  </div>
</template>

<script>           //処理
  export default {
    name: 'Dictionary'
  }
</script>

<style scoped>
                   //修飾
</style>

vue ファイルでは上記の通り、templatescriptstyle の三種類のタグで構成します。

template

template タグ内では、表示内容に関わる記述を行います。通常の HTML と同じような書き方に加え、Vue.js や ElementUI などの、ライブラリ特有のタグを記述することが出来ます。

上記では、後で行うルーティングの設定の結果を確認するため、「辞書」の文字が表示されるようにします。

script

script タグ内では、サーバサイドとのやり取りにより取得したデータや、事前に用意されたデータを、template タグ内にバインドするなどの、動的な処理を書くことが出来ます。

style

style タグ内には、外部ライブラリによるスタイル以外の修飾を記述します。

ルーティングの設定

閲覧者のアクセスに対して、どのページを描画するかを決定する、ルーティングの設定を行います。

src の直下にある router.js を開いて、以下の通りに記述を行います。

router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    },                                         //ここから
    {
      path: '/dictionary',
      name: 'dictionary',
      component: () => import('./views/Dictionary.vue')
    }                                         //ここまで追記
  ]
})

上記のルーティング設定により、/dictionary という URL にリクエストがあれば、Dictionary.vue が描画されます。この時点で再度、ローカル環境で、プロジェクトを起動してみてください。

localhost:8080/dictionary にアクセスして、次のように表示されたら成功です。

Vue実行画面

「Home」と「About」のリンクは今回要らないので、template の記述とルーティングの設定を削除します。

App.vue
<template>
  <div id="app">
    <div id="nav">                                      //この行から
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>                                             //この行まで削除
    <router-view/>
  </div>
</template>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>
router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {                                                                       //この行から
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    },                                                                      //この行まで削除
    {
      path: '/dictionary',
      name: 'dictionary',
      component: () => import('./views/Dictionary.vue')
    }  
  ]
})

再度 localhost:8080/dictionary にアクセスして、「辞書」の文字の上に、「HOME | About」の表示がなくなっているのを確認してください。

axiosのインストール

次に、axios のインストールを行います。axios は Ajax 通信を簡単に実現するためのライブラリです。コンソールで以下のコマンドを実行し、axios のインストールを行ってください。

コンソール
$ npm install axios --save

インストール後、実際に使用できるように、Dictionary.vuescript タグ内でインポートします。

Dictionary.vue
<script>
import axios from 'axios'                                 //追記

export default {
  name: 'Dictionary'
}
</script>

これで、axios を使って、サーバサイドの API を実行することが出来ます。

初期画面の実装

次に、初期画面の実装を行います。

Dictionary.vuescript タグ内に、以下の通りに記述してください。

Dictionary.vue
<script>
import axios from 'axios'

export default {
  name: 'Dictionary',
  data () {                                               //ここから
    return {
      requestForm: {
        fieldId: null
      },
      dictionaries: [],
      fields: []
    }
  }                                                       //ここまで追記
}
</script>

上記のように data 関数内で、requestForm というキーの中に、さらに fieldId というキーを作成します。requestForm オブジェクトには検索条件が格納され、検索を行う際に、その値をサーバサイドに送信します。

requestForm のほかに、dictionariesfields という配列を用意します。この二つの配列には、サーバサイドからのレスポンスが格納されます。

次に data 関数の下に、create 関数を記述します。

Dictionary.vue
<script>
import axios from 'axios'

export default {
  name: 'Dictionary',
  data () {
    return {
      requestForm: {
        fieldId: null
      },
      dictionaries: [],
      fields: []
    }
  },
  created: async function () {                                //ここから
    await this.refresh()
  }                                                           //ここまで追記
}
</script>

create 関数には、最初にこのコンポーネントが生成された際の処理を記述します。

関数を定義する際、async function() と記述することで、非同期処理にあたり、async/await 構文を用いることが出来ます。今回は、下記で追加する refresh 関数を呼び出す処理を書きます。

Dictionary.vue
<script>
import axios from 'axios'

export default {
  name: 'Dictionary',
  data () {
    return {
      requestForm: {
        fieldId: null
      },
      dictionaries: [],
      fields: []
    }
  },
  created: async function () {
    await this.refresh()
  },
  methods: {                                                  //ここから
    refresh: async function () {
      const res = await axios.get('http://localhost:8080/dictionary/')
      this.dictionaries = res.data.responseForm.wordList
      this.fields = res.data.responseForm.fieldList
      console.info(this.dictionaries)
      console.info(this.Fields)
    }
  }                                                           //ここまで追記
}
</script>

refresh 関数には、ページの再読込が行われた際の処理を記述します。

  1. サーバサイドで用意した処理を呼び出し、DB から辞書情報をすべて取得する
  2. 変数 res に、取得した情報を JSON 形式で格納する
  3. 変数 res から、辞書情報を、data 関数で定義した dictionaries に格納する
  4. 変数 res から、カテゴリ情報を、data 関数で定義した fields に格納する

という処理が書かれています。

つまり refresh 関数が呼び出されると、用意されている dictionariesfields に DB に登録されている、辞書情報、分野情報が格納されます。

次に、検索を行う際に使用する searchWord 関数を記述します。

Dictionary.vue
<script>
import axios from 'axios'

export default {
  name: 'Dictionary',
  data () {
    return {
      requestForm: {
        fieldId: null
      },
      dictionaries: [],
      fields: []
    }
  },
  created: async function () {
    await this.refresh()
  },
  methods: {
    refresh: async function () {
      const res = await axios.get('http://localhost:8080/dictionary/')
      this.dictionaries = res.data.responseForm.wordList
      this.fields = res.data.responseForm.fieldList
      console.info(this.dictionaries)
      console.info(this.fields)
    },
    searchWord: async function () {                                        //ここから
      const res = await axios.post('http://localhost:8080/dictionary/search', this.requestForm)
      this.dictionaries = res.data.responseForm.wordList
      console.info(this.dictionaries)
    }                                                                      //ここまで追記
  }
}
</script>

前提として、次回の記事でカテゴリ選択セレクトボックスを作成し、その入力値 requestForm に自動的に格納する機能を追加するものとします。

そのうえで、searchWord 関数では、requestForm に格納されたカテゴリ条件をサーバサイドに渡し、API から条件に合う辞書情報を取得しています。

取得した辞書情報は、data 関数内の dictionaries に格納します。

これで script タグの記述が完了しました。

次回予告

ここまでで、Vue.js によるフロントエンドの実装準備と、サーバサイドとの Ajax 通信処理を紹介しました。

今回やったこと

  • Vue プロジェクトの雛形生成
  • サーバサイドとの通信処理

次回やること

  • 初期画面のテンプレート実装
  • 検索結果表示画面のテンプレート実装

<!-- Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com --><!-- License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->