LoginSignup
1
5

More than 3 years have passed since last update.

RailsアプリでVueを使ってドロップダウンメニューを作成する

Last updated at Posted at 2019-05-02

目標

下記のようなドロップダウンメニューを作成しましたので記載します。
今回はjQueryではなくVue.jsを使って作成してみました。

作成の仕方はVue.jsでなく、普通にjsを使っても全く問題ありませんが、
方法の一つとして、参考になればと思います。

記述量自体はjs書くより減った様な気がします:thinking:

・カテゴリ一覧をドロップダウンで表示させる
・カテゴリーの値はデータベースから取得する
・mouseoverでカテゴリの子要素を表示、mouseleaveで非表示となる
・クリックで各カテゴリページに遷移する

・Rails+Vue.jsで作成する

不備等多々あるかと思いますが、参照記事は良い記事ばかりです。
間違いやより良い記述方法があればご教示ください:relieved:

スクリーンショット 2019-04-23 17.17.16.png
https://i.gyazo.com/24baf3b1c603ee655eb45ecf4d8e4fdf.mp4

①Vue.js導入

Webpackerのインストール

gem 'webpacker', github: 'rails/webpacker'
bundle install

yarnのインストール

brew install yarn

Webpackerインストール

rails webpacker:install


create  config/webpacker.yml
Copying webpack core config
      create  config/webpack
      create  config/webpack/development.js
      create  config/webpack/environment.js
      create  config/webpack/production.js
      create  config/webpack/test.js
Copying postcss.config.js to app root directory
      create  postcss.config.js
Copying babel.config.js to app root directory
      create  babel.config.js
Copying .browserslistrc to app root directory
      create  .browserslistrc
Creating JavaScript app source directory
      create  app/javascript
      create  app/javascript/packs/application.js
       apply  /Users/sotatakahashi/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/webpacker-2387331b33c6/lib/install/binstubs.rb
  Copying binstubs
       exist    bin
      create    bin/webpack
      create    bin/webpack-dev-server
      append  .gitignore
Installing all JavaScript dependencies [4.0.2]
         run  yarn add @rails/webpacker from "."

:
✨  Done in 7.46s.
Webpacker successfully installed 🎉 🍰

↑終わったら絵文字が出ます(かわいい)

Vueインストール

rails webpacker:install:vue

これでRails上でVueを使う準備はOKです!
詳しくは下記記事にて。

参考記事

https://qiita.com/cohki0305/items/582c0f5ed0750e60c951
https://qiita.com/saongtx7/items/fdb77901e7fcf291e2ad

②カテゴリーメニューの作成

マウスオーバーとかは後にして、とりあえず見た目の部分を作っていきます。
(CSS割愛します)
最初に箱をカテゴリの数だけ用意します。

.category_box
 .genre-box
   %ul.genre
     - @parents.each do |parent|
      = link_to category_path(parent.id), method: :get do
        %li.genre_list
         =parent.name
        .next-box
         .sub-box
          %ul.sub
           %li.sub_list
            .detail-box
             %a
              %ul.detail
               %li.detail_list

親、子、孫にそれぞれulとliを持たせて、リストを作成します。
基本はこの箱を使ってドロップダウンメニューを作成していきます!

③子要素リストの切り替えと追加、マウスオーバーで表示/非表示を切り替える

Vue.jsのメソッドのv-on, v-show, v-for, v-bindを使用します。

v-show:

イベント発火のタイミングを決めて、表示と非表示を切り替える。
 (mouseover,mouseleaveで表示非表示を切り替えています)

v-on:

イベント発火のタイミングを決めて、特定のメソッドを動かす。
 (mouseoverでカテゴリ情報を取得し、子カテゴリーがあればAPIで用意します)

v-for:

繰り返し処理を行う
 (選択したカテゴリーの小カテゴリーがあれば次の箱で繰り返しliを作成しています)

v-bind:

データのバインディングを行う
 (v-forで繰り返し作成したliに対してリンクを張っています)

.category_box{"v-show": "menuBool","v-on:mouseleave": "hiddenMenu"}
 #v-showなどはvueで使われるアクション、menuの表示非表示をこちらで設定
 .genre-box
  %ul.genre
   - @parents.each do |parent|
    = link_to category_path(parent.id), method: :get do
     %li.genre_list{'v-on:mouseover': "setCategoryInfo(#{parent.id})"}
      =parent.name
     .next-box{'v-show': 'categoryInfoBool',"v-on:mouseleave": "hiddenSubInfo"}
    #子カテゴリーのメニューの表示、非表示をこちらで設定
      .sub-box
       %a
        %ul.sub
         %li.sub_list{"v-on:mouseover": "setSubInfo(category.id)"}
          {{category.name}}
      #マウスオーバーした時に値を取得、表示させる。{{}}には表示する内容が入り、vueで切り替えられる!
       .detail-box{'v-show': 'SubInfoBool'}
        %a{"v-for": "sub in SubInfo","v-bind:href": "'/categories/' + sub.id"}
         %ul.detail
          %li.detail_list
           {{sub.name}}
new Vue({
      el:"#app",
      data:{
       categoryInfo:[],
        //子カテゴリーの情報を入れる箱
       SubInfo:[],
     //孫カテゴリーの情報を入れる箱
       menuBool: false,
       categoryInfoBool: false,
       SubInfoBool: false,
     //基本非表示にさせるため、falseとしておく
      },
      methods: {
        setCategoryInfo(id){
          axios.get(`/api/genres/${id}.json`)
      //axiosでapiを使用。apiでは取得したカテゴリの子要素を用意する
          .then(res => {
            this.categoryInfo = res.data;
            this.categoryInfoBool = true;
          });
        },
        setSubInfo(id){
          axios.get(`/api/genres/${id}.json`)
          .then(res => {
            this.SubInfo = res.data;
            this.SubInfoBool = true;
          });
        },
        showMenu:function(){
          return this.menuBool = true
        },
        hiddenMenu:function(){
          return this.menuBool = false;
        },
        hiddenCategory:function(){
          return this.categoryInfoBool = false;
        },
        hiddenSubInfo:function(){
          return this.SubInfoBool = false
        },
       //viewに乗っかった時にtrue,falseを切り替える
      }
    });

api/genre
show.json.jbuilder

json.array! @category_children do |category_child|
  json.name category_child.name
  json.id  category_child.id
end

④それぞれにリンクを貼る

%header#app.header
    .header__top
      .header__top__logo
        = link_to root_path do
          %img{alt: "mercari", src: "//www-mercari-jp.akamaized.net/assets/img/common/common/logo.svg?228111635"}/
      .header__top__search
        = form_tag(search_items_path, method: :get, class: "header__top__search__box") do
          %input.header__top__search__box__input{name: "keyword", placeholder: "何かお探しですか?", type: "search"}/
          %button.header__top__search__box__submit{type: "submit"}= fa_icon ("search lg")
          %form{"accept-charset" => "utf-8", :action => "/", :method => "get"}
    .header__bottom
      .header__bottom__leftside{"v-on:mouseleave": "hiddenMenu"}
        .header__bottom__leftside__left_box{"v-on:mouseover": "showMenu","v-on:mouseleave": "hiddenCategory"}
          = link_to categories_path, class: "header__bottom__leftside__left_box__content" do
            = fa_icon ("list-ul")
            %span カテゴリから探す

          -# ここからドロップダウンメニュー
          .category_box{"v-show": "menuBool","v-on:mouseleave": "hiddenMenu"}
            .genre-box
              %ul.genre
              - @parents.each do |parent|
                = link_to category_path(parent.id), method: :get do
                  %li.genre_list{'v-on:mouseover': "setCategoryInfo(#{parent.id})"}
                    =parent.name
            .next-box{'v-show': 'categoryInfoBool',"v-on:mouseleave": "hiddenSubInfo"}
              .sub-box
                %a{"v-for": "category in categoryInfo","v-bind:href": "'/categories/' + category.id"}
 #v-bindでaタグにリンクを貼っつける!
                  %ul.sub
                    %li.sub_list{"v-on:mouseover": "setSubInfo(category.id)"}
                      {{category.name}}
              .detail-box{'v-show': 'SubInfoBool'}
                %a{"v-for": "sub in SubInfo","v-bind:href": "'/categories/' + sub.id"}
                  %ul.detail
                    %li.detail_list
                      {{sub.name}}
          -# ここまでドロップダウンメニュー

        .header__bottom__leftside__right_box
          = link_to '', class: "header__bottom_box__leftside__right_box__content" do
            = fa_icon ("tag")
            %span ブランドから探す
      .header__bottom__rightside
        .header__bottom__rightside__bell
          = link_to '', class: "header__bottom__rightside__bell__content" do
            = fa_icon("bell lg")
            %span お知らせ
        .header__bottom__rightside__check
          = link_to '', class: "header__bottom__rightside__check__content" do
            %span
              = fa_icon("check lg")
              %span やることリスト
        .header__bottom__rightside__mypage
          = link_to '', class: "header__bottom__rightside__mypage__content" do
            %img.header__bottom__rightside__mypage__content__icon{alt: "", src: "//static.mercdn.net/images/member_photo_noimage_thumb.png", width: "32"}/
            %div マイページ
new Vue({
      el:"#app",
      data:{
       categoryInfo:[],
       SubInfo:[],
       menuBool: false,
       categoryInfoBool: false,
       SubInfoBool: false,
      },
      methods: {
        setCategoryInfo(id){
          axios.get(`/api/genres/${id}.json`)
          .then(res => {
            this.categoryInfo = res.data;
            this.categoryInfoBool = true;
          });
        },
        setSubInfo(id){
          axios.get(`/api/genres/${id}.json`)
          .then(res => {
            this.SubInfo = res.data;
            this.SubInfoBool = true;
          });
        },
        showMenu:function(){
          return this.menuBool = true
        },
        hiddenMenu:function(){
          return this.menuBool = false;
        },
        hiddenCategory:function(){
          return this.categoryInfoBool = false;
        },
        hiddenSubInfo:function(){
          return this.SubInfoBool = false
        },
      }
    });

かんせい!
Vue楽しいので何か違うこともやってみたいです!コンポーネントをそれぞれ作って表示とかも楽しそう:unicorn:

参考記事

インストール〜作成までの流れ
https://qiita.com/cohki0305/items/a678b0b17c5b496c1de9
https://qiita.com/saongtx7/items/fdb77901e7fcf291e2ad
axiosの使い方
https://www.webprofessional.jp/fetching-data-third-party-api-vue-axios/
https://qiita.com/ryouzi/items/06cb0d4aa7b6527b3645
vue.jsでmouseoverした時だけ表示する方法
https://qiita.com/sukechansan/items/07a415e4e7e5ce358afc
vue.jsでのリンクの貼り方
https://qiita.com/asaokamei/items/6afa7e2f33207d041588

1
5
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
5