Nuxt.jsでvue-materailを使ってドロワーやモーダルを使用してみよう($ref)

コンポーネントの親子関係でrefを使ってみよう

はじめに

こんにちは。Kennyです。

今回はvue-materialでドロワーやモダールの使い方を解説していきます。

vue-materialはスタイルのパッケージです。アプリケーションにインストールしてNuxt.jsのplugins,nuxt.config.jsの登録やrefを使ったコンポーネントでのやりとりを中心にやってみたいと思います。

前提

開発環境

macOS Catalina

MacBook Pro (15inch, 2019)

npm version 6.14.4

nodebrew use v14.0.0(node)

@vue/cli 4.3.1

yarn 1.22.4

Nuxt.js v2.12.2

vue-material ^1.0.0-beta-14

Nuxt.js のプロジェクトを始める

Nuxt.jsはVue.jsの拡張フレームワークです。Vue.jsアプリケーションを作成したことがある方はすぐに始められると思います。この記事ではNuxt.jsの始め方の解説は省かせていただきます。

vue-materailのインストール

npm i vue-material

インストール出来たらpluginsにvue-material.jsを作成してください。

plugins/vue-materail.jsの作成しよう

import Vue from 'vue'
import VueMaterial from 'vue-material'

Vue.use(VueMaterial)

nuxt.config.jsに登録しよう

nuxt.config.jsのpluginsに作成したファイルのパスを指定します。

plugins: [
 '@/plugins/vue-material'
],

次にnuxt.config.jsのcss構成オブジェクトに記述します。

css: [
  { src: 'vue-material/dist/vue-material.min.css', lang: 'css' },
  { src: 'vue-material/dist/theme/default.css', lang: 'css' }
],

vue-material公式にあるこちらがこの記述にあたります。

vue-materialのアイコンやフォントを使おう

nuxt.config.jsに次を追加してください。

link: [
 { rel: 'stylesheet', href: '//fonts.googleapis.com/css?family=Roboto:400,500,700,400italic|Material+Icons' }
]

ナビゲーションバーを作成してみよう

ナビゲーションバーを作成してみましょう。

左上のハンバーガーメニューをクリックすることでドロワーが開き、検索ボタンをクリックすることでモーダルが開きます。

Header.vueコンポーネントを作成しよう

componentsフォルダーにHeader.vueを作成し、vue-materialの素材を使ってナビバーを作成しましょう。そして、どのページでも使いたいコンポーネントなのでlayoutsのdefault.vueにimportします。

まずは全てのコンポーネントを作成します。

<template>
  <div>
    <md-toolbar class="fixed-toolbar" elevation="1">
      <!-- toggle button -->
      <md-button class="md-icon-button" @click.stop="openLeftDrawer()">
        <md-icon>menu</md-icon>
      </md-button>
      <!-- toggle button -->
      <nuxt-link class="md-primary md-title" to="/">
        ヘッダー
      </nuxt-link>
      <div class="md-toolbar-section-end">
        <template>
          <md-button>
            ログアウト
          </md-button>
        </template>
        <template>
          <md-button @click="$router.push('/login')">
            ログイン
          </md-button>
          <md-button @click="$router.push('/register')">
            登録
          </md-button>
        </template>
        <md-button
          class="md-primary"
          @click="openSearchDialog()"
        >
          検索
        </md-button>
        <md-button @click.stop="show = true">
          カテゴリー
        </md-button>
      </div>
    </md-toolbar>
    <template>
      <LeftSidePanel ref="leftDrawer" />
    </template>
    <SearchModal ref="dialog" />
  </div>
</template>

import LeftSidePanel from '@/components/LeftSidePanel'
import SearchModal from '@/components/SearchModal'

export default {
  components: {
    LeftSidePanel,
    SearchModal
  },
  data () {
    return {
      show: false
    }
  },
  methods: {
    openLeftDrawer () {
      this.$refs.leftDrawer.open()
    },
    openSearchDialog () {
      this.$refs.dialog.open()
    }
  }
}
</script>

LeftSidePanel.vueを作成しよう

LeftSidepanelはドロワーのコンポーネントで、SearchModalはモーダルですね。 次にそれらのコンポーネントをみていきます。

<template>
  <div>
    <md-drawer :md-active.sync="leftDrawer" md-fixed>
      <md-toolbar md-title :md-elevation="1">
        ドロワー
      </md-toolbar>
      <md-progress-bar md-mode="indeterminate" />
      <!-- feed お気に入り -->
      <md-empty-state
        class="md-primary"
        md-icon="book"
        md-label="Feedはありません"
        md-description="ブックマークするにはログインして下さい"
      >
        <md-button
          class="md-primary md-raised"
          @click="$router.push('/login')"
        >
          ログイン
        </md-button>
      </md-empty-state>

      <md-empty-state
        class="md-accent"
        md-icon="bookmark"
        md-label="Feedはありません"
        md-description="ブックマークしたものは、ここに安全に保管されます"
      />
      <!-- 今回触るfeed list -->
      <md-list
        class="md-triple-line"
      >
        <md-list-item>
          <md-avatar>
            <img>
          </md-avatar>
          <div class="md-list-item-text">
            <span><a target="_blank">{{ }}</a></span>
            <span>{{ }}</span>
            <span>コメントを表示</span>
          </div>
          <md-button
            class="md-icon-button md-list-action"
          >
            <md-icon class="md-accent">
              delete
            </md-icon>
          </md-button>
        </md-list-item>
        <md-divider class="md-inset" />
      </md-list>
      <feed-list />
    </md-drawer>
  </div>
</template>

<script>
export default {
  data () {
    return {
      leftDrawer: false
    }
  },
  methods: {
    open () {
      this.leftDrawer = true
    },
    close () {
      this.leftDrawer = false
    }
  }
}
</script>

SearchModal.vueを作成しよう

検索ボタンをクリックした際に出現するモーダルです。

<template>
  <md-dialog :md-active.sync="dialog">
    <md-dialog-title>{{ text }}</md-dialog-title>
    <div class="md-layout" style="padding: 1em;">
      <md-field>
        <label>検索用語</label>
        <md-input
          v-model="query"
          placeholder="完全に一致する場合は引用符を使用し、複数の用語にはAND/OR/NOTを使用します"
          max-length="30"
        />
      </md-field>
      <md-datepicker
        v-model="fromDate"
        md-immediately
      >
        <label>開始日を選ぶ</label>
      </md-datepicker>
      <md-datepicker
        v-model="toDate"
        md-immediately
      >
        <label>終了日を選ぶ</label>
      </md-datepicker>
      <md-field>
        <label for="sortBy">条件で検索結果を並べ替える</label>
        <md-select
          id="sortBy"
          v-model="sortBy"
          name="sortBy"
          md-dense
        >
          <md-option value="publishedAt">
            新着
          </md-option>
          <md-option value="relevancy">
            関連
          </md-option>
          <md-option value="popularity">
            人気
          </md-option>
        </md-select>
      </md-field>
    </div>
    <md-dialog-actions>
      <md-button class="md-accent" @click="close()">
        Cancel
      </md-button>
      <md-button class="md-primary" @click="searchHeadlines">
        検索
      </md-button>
    </md-dialog-actions>
  </md-dialog>
</template>

<script>
export default {
  data () {
    return {
      dialog: false,
      text: '検索する'
    }
  },
  methods: {
    searchHeadlines () {
    },
    open () {
      this.dialog = true
    },
    close () {
      this.dialog = false
    }
  }
}
</script>

default.vueにHeader.vueをimportしよう

default.vueにインポートされたコンポーネントはどのルーティングでも表示される仕様になってます。ヘッダーをインポートしましょう。

<template>
  <div>
    <Header />
    <nuxt />
  </div>
</template>

<script>
import Header from '@/components/Header'
export default {
  components: {
    Header
  }
}
</script>

<style>

</style>

propsでもないのになぜドロワーが開くのでしょう?

md-iconのハンバーガーをクリックすることでドロワーを開くことが出来ます。

処理の関数はopenLeftDrawer関数ですが処理内容は次になります。

openLeftDrawer () {
  this.$refs.leftDrawer.open()
},

leftDrawerとは開くドロワーに用意されたdataです。

data () {
  return {
    leftDrawer: false
  }
}

次にopen関数ですが、これも開くドロワーに用意されたメソッドです。

open () {
  this.leftDrawer = true
},

クリック時の処理関数は、子コンポーネントのデータやメソッドにアクセスした処理ということで、アクセスするのに$refsを使用します。

refの値は$refsでアクセスできる

親コンポーネントと子コンポーネント間のデータの受け渡しがpropsやemitで通常行われるのに対し、ref(参照)を使えば簡単に親子間のデータにアクセスできるというワケです。

開閉方法はLeftSidePanel.vueの

:md-active.sync="leftDrawer"

この部分にあります。vue-material特有の開閉ロジックですが、受け渡されたleftDrawerが作用しています。

SearchModal.vueも同じように$refsでアクセス

小コンポーネントのdataプロパティにdialogが定義されています。この値にアクセスするために次のようにします。

<SearchModal ref="dialog" />

このように参照します。

SearchModal.vueに定義されたdialog: falseの値を変更するメソッドをopenとしてモーダル側に定義していますね。このopen関数に親コンポーネントからアクセスします。

openSearchDialog () {
  this.$refs.dialog.open()
}

この関数は親コンポーネントの検索ボタンに定義された関数です。

このように子コンポーネントに定義されたメソッドを操作する方法がprops以外にあることがわかりました。

さいごに

今回はrefを使った親子コンポーネントの操作を解説しました。propsと違ったレイジーな方法かもしれませんが子コンポーネントにアクセスする一つの方法としてよく使われていますので覚えておいて損はないと思います。

SNSでもご購読できます。

検索