1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails6 + Vue.js】ブックマークを管理する簡単なSPAを作ってみた話(第四章:ローディング表示、ページネーション、ユーザー管理機能の実装)

Posted at

前回の記事の続きになります。
前回の記事はこちら↓

本記事の概要

今回実装する機能について

【ローディング表示機能】

  • vue-loading-template を導入してローディングを表示させます。

【ページネーション機能】

  • gem で kaminari を導入して実装します。

【ユーザー管理機能】

  • 今回は devise で実装します。

記事構成

本アプリケーションについての記事は大きく4部構成で作成しております。

本記事の参考URL

本記事の作成にあたって主に以下のドキュメント・記事を参考にさせて頂きました。

実装

それでは実装に移ります。

ローディング表示機能

vue-loading-templateを導入して実装します。
参考記事↓

% yarn add vue-loading-template

app.vueを編集

app/javascript/app.vue
<template>
  <v-app id="app">
  <!-- ローディングを表示 -->
  <Loading v-show="isLoading"></Loading>
    <v-container>

      <!-- 中略 -->

    </v-container>
  </v-app>
</template>

<script>
import draggable from 'vuedraggable'
import Loading from './components/Loading'  // Loading コンポーネントをインポート
import axios from 'axios';

export default {
  data: function () {
    return {
      isLoading: true,
      bookmarkList: ['',''],
      allData: ['',''],
      
      ...

    }
  },
  mounted () {
    axios.get("/api/bookmarks")
    .then(response => {
        this.allData = response.data;
        this.isLoading = false;  // マウントしたらfalseに変更
      }
    );
    this.setBookmark();
  },
  components: {
    Loading,  // Loagingコンポーネントを追加
    draggable,
  },
  methods: {
  ...

  }
}
</script>

components フォルダ配下に Loading.vue を設置して実装

app/javascript/components/Loading.vue
<template>
  <div v-show="isLoading">
    <div class="fullview">
      <div class="loading-spacer"></div>
      <!-- spiningDubblesというタイプのローディングを実装 -->
      <vue-loading 
        type="spiningDubbles"
        color="#2f8af0"
        :size="{ width: '100px', height: '100px' }"
        >
      </vue-loading>
    </div>
  </div>
</template>

<script>
import { VueLoading } from 'vue-loading-template'

export default {
  name: 'isLoading',
  data() {
    return {
      isLoading: true,
    }
  },
  components: {
    VueLoading,
  },

}
</script>

<style>
.fullview {
  width: 100%;
  height: 100%;
  background: rgba(254, 254, 254, 0.8);
  position: fixed;
  top: 60px;
  left: 0;
  z-index: 5;
}
.loading-spacer {
  height: 30%;
}
</style>

以上でローディング表示機能の実装は終了です。

ページネーション機能

kaminariを導入して実装します。
参考記事↓

gem 'kaminari'
% bundle install

bookmarks_controllerの編集

app/controllers/api/bookmarks_controller.rb
class Api::BookmarksController < ApplicationController

  def index
    page = params[:page] || 1  # 表示するページの指定
    per = params[:per] || 30   # 1ページあたり30件のブックマークを表示

    @bookmarks = Bookmark.order('created_at DESC').page(page).per(per)
    total_pages = @bookmarks.total_pages  # 全ページ数を取得

    response = {
      bookmarks: @bookmarks,
      total_pages: total_pages,
    }
    render json: response
  end

end

app.vueの編集

【 Vue.js の編集】

app/javascript/app.vue
<script>
export default {
  data: function () {
    return {
      isLoading: true,
      bookmarkList: ['',''],

      ...

      currentPage: 1,    // 表示するページの番号
      itemsPerPage: 30,  // 1ページあたりの表示件数
      totalPages: null,  // ページネーションした時の全ページ数
    }
  },

  ...

  methods: {
    setBookmark: function () {
      // Axios で api にリクエスト
      const url = `/api/bookmarks?page=${this.currentPage}?per=${this.itemsPerPage}`;
      axios.get(url)
      .then(response => {
        // ブックマークの全データを取得
        this.allData = response.data.bookmarks
        this.bookmarkList = this.allData

        // ページネーションの全ページ数を取得
        this.totalPages = response.data.total_pages

        this.listCategories();
        this.abstruct();
        }
      );
    },

    ...

  }
}
</script>

【template の編集】

app/javascript/app.vue
<template>
  <v-app id="app">
  <Loading v-show="isLoading"></Loading>
    <v-container style="height: 1000px; max-width: 2400px; padding: 0 20px;">
      <v-layout>

        <v-flex xs8>
          <div style="width: 100%; margin: 5px 0 20px 0; display: flex; justify-content: center;">
            <h1>Bookmark 一覧</h1>
          </div>
          
          <v-layout>
            <v-flex row wrap style="justify-content: center;">
              <draggable v-model="bookmarkList" style="margin: 0 25px; width: 80%; cursor: pointer;">
                <v-card v-for="bookmark in bookmarkList" :key="bookmark.id" :items-per-page="itemsPerPage" style="width: 100%">
                  <v-card-title primary-title style="margin-bottom: 15px; width: 100%; padding-bottom: 10px;">
                    <div style="width: 100%;">
                      <div class="headline mb-0" style="display: flex; justify-content: space-between; width: 100%">
                        <a v-bind:href="bookmark.url" target="_blank" rel="noopener noreferrer" style="font-size: 18px;">
                          {{ bookmark.title }}
                        </a>
                        
                        <v-tooltip right>
                          <template v-slot:activator="{ on }">
                            <v-btn light v-on="on" @click="togglePutModal(bookmark.id)" style="margin-bottom: 8px">
                              <span class="material-icons" style="margin-right: 4px;">create</span>
                            </v-btn>
                          </template>
                          <span>編集する</span>
                        </v-tooltip>

                      </div>
                      <v-divider></v-divider>
                      <div style="font-size: 16px; display: flex; justify-content: space-between; width: 100%">
                        <div>#{{ bookmark.category }}</div>
                        
                        <v-tooltip right>
                          <template v-slot:activator="{ on }">
                            <v-btn dark v-on="on" @click="toggleDeleteModal(bookmark.id)" style="margin-top: 8px">
                              <span class="material-icons" style="margin-right: 4px;">delete</span>
                            </v-btn>
                          </template>
                          <span>削除する</span>
                        </v-tooltip>

                      </div>
                    </div>
                  </v-card-title>
                </v-card>
              </draggable>
            </v-flex>
          </v-layout>

          <!-- ページネーション表示部分を追加 -->
          <div class="text-xs-center" style="margin: 20px 0 40px 0;">
            <v-pagination
              v-model="currentPage"
              :length="totalPages"
              @input="setBookmark"
            ></v-pagination>
          </div>
        </v-flex>

      </v-layout>
    </v-container>
  </v-app>
</template>

以上でページネーション機能の実装は終了です。

ユーザー管理機能の実装

deviseを導入して実装します。(新規登録ページやログインページの実装に関しては割愛します。)
本来は最初に実装するべきですが、今回はブックマークの機能(Vue.js)を主な目的としているため後から実装しています。

gem 'devise'

bundle installしてdeviseをインストール

% bundle install
% rails g devise:install

bookmarkのマイグレーションファイルをロールバックして編集

db/migrate/2021xxxxxxxxx2_create_bookmarks.rb
class CreateBookmarks < ActiveRecord::Migration[6.0]
  def change
    create_table :bookmarks do |t|
      t.string :title, null: false
      t.string :url, null: false
      t.string :category, null: false
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end

userモデルの作成

% rails g devise user
db/migrate/2021xxxxxxxxxx1_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :nickname,            null: false, default: ""
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at
      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
  end
end
% rails db:migrate

application_controllerの編集

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # 登録に必要なストロングパラメータを設定
  before_action :configure_permitted_parameters, if: :devise_controller?
  # ユーザーがログインしていない場合はログインページを表示
  before_action :authenticate_user!

  private
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
  end
end

それぞれのモデルを編集(アソシエーションの記述)

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :bookmarks  # 一人のユーザーは複数のブックマークのデータを扱う

  # バリデーション設定
  validates :nickname, presence: true
  PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]+\z/i.freeze
  validates_format_of :password, with: PASSWORD_REGEX, message: 'には英字と数字の両方を含めて設定してください'
end
app/models/bookmark.rb
class Bookmark < ApplicationRecord
  belongs_to :user
  
  with_options presence: true do
    validates :url, format: {with: /\A#{URI::regexp(%w(http https))}\z/}
  end
end

bookmarks_controllerの編集

app/controllers/api/bookmarks_controller.rb
class Api::BookmarksController < ApplicationController
  protect_from_forgery :except => [:create, :update, :destroy]

  def index
    page = params[:page] || 1
    per = params[:per] || 30

    # ログインしているユーザーのブックマーク情報を取得
    @bookmarks = Bookmark.where(user_id: current_user.id).order('created_at DESC').page(page).per(per)
    total_pages = @bookmarks.total_pages

    response = {
      bookmarks: @bookmarks,
      total_pages: total_pages,
    }
    render json: response
  end
end

以上でユーザー管理機能の実装は終了です。
また、今後学習を深めてJsonWebToken(JWT)によるユーザー認証機能の実装にも挑戦したいと思います。

最後に

以上でブックマーク個人管理アプリケーションの実装は全て終了です。
いかがだったでしょうか?
未だ記事を書くことに慣れていない為分かりづらい部分や読みにくい部分があるかもしれませんが今後修正点や改善点を気付き次第修正をしていこうと思います。

読んでいただき、ありがとうございました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?