トップイメージ

今回実装したもの

前回に続いて、Rails + vue.js(vuetify)を利用してユーザー管理画面を作りました。

今回実装したものは、前回の最後で予告していた通り、画面左のテーブルで選択した列を削除する機能・右側のフォームに空欄を許さないなどのバリデーションを付与しました。

完成ユーザー管理画面

ソースコード

一応今回で一旦完成ということで、関係するファイルのソースを全てあげたいと思います。 ↑イメージを作っているファイル

フロントサイド(vue + vuetify)

<!-- user_index.vue -->

<template>
<v-app>
    <v-container>
    <!-- Userのデータテーブル -->
    <v-layout class="table" row wrap>
        <v-flex xs6>
            <grid
            :headers="headers"
            :contents="users"
            @grid_tr_click="InputForm"
            @trDelete="del"
            >
            </grid>
        </v-flex>   
        <v-flex xs5 class="ml-5">
          <template>
              <v-text-field
                label="ID"
                value=""
                hint="ユーザーID"
                id="user_id"
                v-model="id"
                prepend-icon="person"
                 :rules="noEmptyRules"
              >
              </v-text-field>
              <v-text-field
                label="氏名"
                value=""
                hint="ユーザー氏名"
                id="user_simei"
                v-model="simei"
                prepend-icon="domain"
                :rules="nameRules"
                :counter="20"
              >
              </v-text-field>
              <v-text-field
                label="氏名かな"
                value=""
                hint="ユーザー氏名かな"
                id="user_simei_kana"
                v-model="simei_kana"
                prepend-icon="domain"
                :rules="nameRules"
                :counter="20"
              >
              </v-text-field>
              <v-text-field
                label="所属"
                value=""
                hint="所属情報"
                id="user_syozoku"
                v-model="syozoku"
                prepend-icon="group"
                :rules="nameRules"
                :counter="20"
              >
              </v-text-field>              
               <v-menu
                  v-model="menu2"
                  :close-on-content-click="false"
                  :nudge-right="40"
                  lazy
                  transition="scale-transition"
                  offset-y
                  full-width
                  min-width="290px"
                >
               <template v-slot:activator="{ on }">
                <v-text-field
                  v-model="seinen_ymd"
                  label="生年月日"
                  prepend-icon="event"
                  v-on="on"
                  id="seinen_ymd"
                  :rules="noEmptyRules"
                ></v-text-field>
              </template>
              <v-date-picker v-model="seinen_ymd" @input="menu2 = false"></v-date-picker>
               </v-menu>

              <v-layout justify-end>
                <v-btn color="success" v-on:click="hozon">保存</v-btn>
                <v-btn color="error" v-on:click="clear">クリア</v-btn>
              </v-layout>
          </template>
        </v-flex>
    </v-layout>
  </v-container>
</v-app>
</template>


<style scoped>

</style>

<script>
import Axios from 'axios'
import Grid from '../base_conponent/grid.vue'
import Datepicker from 'vuejs-datepicker';

export default{
  components: {
    "grid":Grid,
    "date-pciker":Datepicker
  },
  data () {
    return {
      //グリッドに詰めるデータ用の箱
      users:[],
      //ヘッダーをここで指定
      headers: [
          {
            text: 'ID',
            align: 'center',
            sortable: true,
            value: 'id',
            class:'true'
          },
          {
            text: '氏名',
            align: 'center',
            sortable: true,
            value: 'name',
            class:"true",
          },
          { text: 'ふりがな', 
            align:"center",
            value: 'name_kana',
            sortable: true,
            class:"true" },
            { text: '所属', 
            align:"center",
            value: 'syozoku',
            sortable: true,
            class:"false" },
            { text: '生年月日', 
            align:"center",
            value: 'seinen_ymd',
            sortable: true,
            class:"false" },
        ],
        //data-table検索用(無くても良い)
        search: '',
        //グリッド右のテキストボックスにデータを詰めるため
        id:"",
        simei:"",
        simei_kana:"",
        syozoku:"",
        //date-picker用
        seinen_ymd: "",
        menu2: false,
        //formのバリデーションルール
          //空欄を許さない
        noEmptyRules: [
        v => !!v || '必須入力です',
        ],
          //文字数制限
        nameRules: [
        v => !!v || '必須入力です',
        v => v.length <= 20 || '20文字以内で入力してください',
      ],

    }
  },
  mounted(){
      let self = this;
      //data-table用のデータをrailsサーバーから取得
      Axios.get('/users/search')
        .then((res) => {
            self.users = res.data.data;
        })
        .catch(error => {
          alert("データ取得失敗");
        })
  },
  methods:{
    //左側のテーブルの列選択時に右側のフォームへデータを飛ばす
    InputForm:function(data){
      this.id = data.currentTarget.cells[0].innerHTML.replace(/ /g, '').replace(/\r?\n/g,"");
      this.simei = data.currentTarget.cells[1].innerHTML.replace(/ /g, '').replace(/\r?\n/g,"");
      this.simei_kana = data.currentTarget.cells[2].innerHTML.replace(/ /g, '').replace(/\r?\n/g,"");
      this.syozoku = data.currentTarget.cells[3].innerHTML.replace(/ /g, '').replace(/\r?\n/g,"");
    },
    hozon:function(data){
          var yesNo = confirm("保存します。よろしいですか?")
          if (yesNo){
              //Userモデルへの保存処理
              const token = document.getElementsByName("csrf-token")[0].getAttribute("content");
              Axios.defaults.headers.common["X-CSRF-Token"] = token;
              Axios.post('/users/' ,{
                user:{
                  id:this.id,
                  name:this.simei,
                  name_kana:this.simei_kana,
                  syozoku:this.syozoku,
                  seinen_ymd:this.seinen_ymd.replace(/[^0-9]/g, '')
                }
              })
              .then((res) => {
                  alert('保存成功')
                  this.users = res.data.data
              })
              .catch(error => {
                  console.log(error)
              });
          }
      },
      clear:function(){
        this.id = "";
        this.simei = "";
        this.simei_kana = "";
        this.syozoku = "";
        this.seinen_ymd = "";
      },
      del:function(){
        //Userモデルの削除リクエスト送信
        const token = document.getElementsByName("csrf-token")[0].getAttribute("content");
              Axios.defaults.headers.common["X-CSRF-Token"] = token;
              Axios.delete('/users/' + this.id ,{
                user:{
                  id:this.id,
                  name:this.name
                }
              })
              .then((res) => {
                  alert('削除成功')
                  this.users = res.data.data
              })
              .catch(error => {
                  console.log(error)
              });
      }
  }
  
}
</script>

↑で呼び出している左側のデータテーブルのコンポーネント(部品)

<!-- grid.vue -->
<template>
<v-app>
    <v-flex class="text-xs-center" mb-5>
        <!-- tableを動的に表示 -->
        <v-data-table
            :headers="headers"
            :items="contents"
            class="elevation-1 w-100"
            load = true
            :search="search"
            light
            :pagination.sync="pagination"
        >
            <template slot="items" slot-scope="myprops">
                <tr v-on:click="trClick">
                <!-- 親コンポーネントで指定したヘッダーの値だけ表示 -->
                    <td v-for="header in headers" v-show="header.class">
                    {{ myprops.item[header.value] }}
                    </td>
                </tr>
            </template>
            <template v-slot:footer >
                <td :colspan="headers.length">
                    <v-card>
                        <v-card-title>
                        検索
                        <v-spacer></v-spacer>
                        <v-text-field
                            v-model="search"
                            append-icon="search"
                            label="検索"
                            single-line
                            hide-details
                        ></v-text-field>
                        </v-card-title>
                    </v-card>
                    <v-card>
                        <v-card-title>
                         <v-btn color="error" v-on:click="trDelete">削除<v-icon>delete</v-icon></v-btn>
                        <v-spacer></v-spacer>
                        </v-card-title>
                    </v-card>
                </td>
            </template>
            <!-- 検索が見つからない場合 -->
            <template v-slot:no-results>
                <v-alert :value="true" type="error" icon="warning">
                    "{{ search }}" が見つかりませんでした
                </v-alert>
            </template>
            
        </v-data-table>
    </v-flex>
</v-app>
</template>


<style>

</style>

<script>
import Axios from 'axios'

export default{
  props:{
      headers:Array,
      contents:Array
  },
  data () {
    return {
      no_data_flg: false,
      search: '',
      pagination: {
        sortBy: 'content',
        descending: true,
        rowsPerPage: 10,
      },
    }
  },
  methods:{
      trClick:function(e){
          //親画面にイベントとデータを引き渡す
          this.$emit('grid_tr_click',e)
      },
      trDelete:function(e){
          //親画面にイベントとデータを引き渡す
          this.$emit('trDelete')
      }
     
  }
}
</script>

サーバーサイド(Rails)

フロント側にデータを渡したり、データを受け取ってデータの更新・削除などを行う処理を担っている。コントローラーです。

#users_controller.rb
class UsersController < ApplicationController
  def index
  end

  def create
    #投稿されたものが新規かどうか判別
    user = User.find_or_initialize_by(id:user_params[:id])
    #idでレコードが見つからなければ、新規作成
    if user.new_record?
      user = User.new(user_params)
      user.save!
    #idでレコードが見つかれば、更新
    else
      user.update(user_params)
    end
    search()
  end

  def destroy
    user = User.find(params[:id])
    user.delete
    search()
  end

  def search
    users = User.all.order(id:'desc')
    render json:{total:users.size,data:users}
  end

  private

  def user_params
    params.require(:user).permit(:id,:name,:name_kana,:syozoku,:seinen_ymd)
  end

end

フロント側からのリクエストに応じて、処理を振り分けるルーター

#routes.rb

Rails.application.routes.draw do
  resources:users, :only => [:index,:create,:destroy] do 
    collection do
      get 'search'
    end
  end
end

一応終了

というわけで3回に分けました、ユーザー管理画面の作成について終了いたします。

vuetifyを使えば簡単にフロントサイドを作り込むことができました。 無論、まだまだ筆者が使いこなせていないところが多いので、不要なソースが多いと思います。

よく、Web系エンジニアを目指す人が、ECサイトを作成できるように頑張ってポートフォリオを作っていると思いますが、実際初心者プログラマーに最初に任せられるのはこのような管理画面だったりするので、練習してみるのも良いかと思います。