14
10

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 5 years have passed since last update.

vue.js でボタンの活性非活性を実現するぞ♩…が簡単じゃなかった

Last updated at Posted at 2018-12-09

vue.js でボタンの活性非活性をリアクティブに操作しよう

やりたい事ですが、「A、B、C」というボタンがあり最初はみんな活性状態です。
ボタン押下可能な状態ってやつですね。

「A」のボタンを押したら、「B、C」のボタンを非活性にする。
「B」のボタンを押したら、「A、C」のボタンを非活性にする。
という押したボタン以外のボタンを非活性にしたい。

これだけ見ると、Vue.jsなら簡単に実装できる!と思ってました。

ざっくりと実現に向けた実装方法

vue-cli にてinit を走らせてプロジェクトを作成します。
お気に入りの魔法のコマンドがあり、こちらの記事を拝見してから以下の方法で作成してます。

vue init bootstrap-vue/webpack-simple my-project

ざっくりと構成見本

展開されたファイルのVueファイルをいじって、下記のようなコンポーネントの構成を作りました。

my-project
  ┣ dist
  ┣ node_modules
  ┣ src
  ┃    ┣ component
  ┃    ┃    ┣ Panel01.vue
  ┃    ┃    :
  ┃    ┣ App.vue
  ┃    ┣ Navigation.vue
  ┃    ┣ MainPanel.vue
  ┃    ┗ main.js
  ┣ package-lock.json
  ┣ package.json
  :

main.js でApp.vueのマウントを記述し、App.vueの中でrouter-viewsを記載して、
MainPanelの中のコンテンツ(Panel01)を切り替えるような構成です。

ボタン活性切り替えの方法

MainPanelの中にButtonを仕込んで、Panelxx.vueを切り替えようとしていたので、
以下のような記述をしていました。こちらもざっくり必要箇所だけ記載します。

<template>
  <div id="main-panel" class="content-ground p-0 m-0">
    <div class="content-base d-flex p-1 border border-dark">
      <div id='quest-container' class="content-ground p-0 m-0">
        <b-container class="content-main border border-dark">
          <panel01></panel01>
          <panel02></panel02>
          <panel03></panel03>
        </b-container>
      </div>
      <ul class="d-inline">
        <li>
          <button id="push-btn-1" class="btn"
                  v-on:click="pushedBtn(1)" :disabled="isPush1">button 1</button>
        </li>
        <li>
          <button id="push-btn-2" class="btn"
                  v-on:click="pushedBtn(2)" :disabled="isPush2">button 2</button>
        </li>
        <li>
          <button id="push-btn-3" class="btn"
                  v-on:click="pushedBtn(3)" :disabled="isPush3">button 2</button>
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
// childrens
import Panel01 from './component/Panel01.vue'
import Panel02 from './component/Panel02.vue'
import Panel03 from './component/Panel03.vue'

export default {
  name: 'main-panel',
  components: {
    Panel01, Panel02, Panel03
  },
  data: () => {
    return {
      isPush1: false,
      isPush2: false,
      isPush3: false,
    }
  },
  methods: {
    pushedBtn: (s) => {
      if(s === 1){
        this.isPush1 = false;
        this.isPush2 = true;
        this.isPush3 = true;
      } else if ( s === 2) {
        this.isPush1 = true;
        this.isPush2 = false;
        this.isPush3 = true;
      } else if ( s === 3) {
        this.isPush1 = true;
        this.isPush2 = true;
        this.isPush3 = false;
      }
    }
  },
  created() {
    console.log(this)
  }
}
</script>

ボタン活性非活性をいざお試し

上記の記述でローカルで動作確認すると、ボタンの活性状態が変化しません。
おかしいなと思い、console.log で該当のbool値を出力してみたり、
vue-devtoolsで確認してみました。
「pushBtn」メソッドの最後にbool値を出力するとちゃんと押したボタン以外はtrueで
'disabled'をonにしてくれます。が、ボタンは変化なし。
vue-devtoolsで値を見ると、全てfalseとなっていて変わっていません。
ムムムと思い試行錯誤しまくって、以下の事実を発見。

VueComponentとして認識されていない

今回の構成の親子関係を表現すると、

root
  ┗App
      ┣ Navigation
      ┗ MainPanel
          ┗ Panel01

このようなツリーとなります。
MainPanel.vue内のmethodsで、試しにconsole.log(this)を出力してみました。
結果、こいつは「object」扱いで、dataで宣言した値も含まれていましたが、componentのプロパティらしきものは
「a」という変数に格納されていました。(もちろん活性切り替えの値は変更されず)
App.vueで同じようにmethods内にthisを出力させたところ、こちらはVueComponentになっていました。


▼Appでthisを出力
(変数名とか記事用に編集している関係でスクショと合わない所があります)
スクリーンショット 2018-12-10 1.48.37.png


▼MainPanelでthisを出力 (同上の事情)
スクリーンショット 2018-12-10 1.49.49.png


子コンポーネントがVueComponentにならない

おそらく親子関係の親になる部分はリアクティブになるのですが、
子コンポーネントに当たるMainPanel以下はコンポーネントの扱いにならないようです。

しかし、textarea と labelを組み合わせた、「文字を入力したらラベルにプレビューされる」という
動作はMainPanel以下でも可能でした。elementからのInputイベントとv-modelを使用できる場合は、
リアクティブが生きるようです。なんでだ…??

そして、コンポーネント扱いにならないため、リアクティブに動作するためにvueが検知できるように登録する
this.$setメソッドもundifined になってしまいました。そもそも $で引ける関数がない状態です。

原因究明に恐ろしい時間を費やす

あれこれ様々な記事を読み、公式ドキュメント等も読んで試行錯誤してみましたが、
「ボタンの非活性活性はv-bindすれば data のbool値を変えてできるよ♩」というのと、
「リアクティブに動作させるにはvueに値をset もしくはObject.assign()を使おう!」ということにしか行き着かず、
object扱いになってしまっている原因が探せず仕舞い。
teratailにも質問投げてみましたが解決の兆しは無く、2週間ほどこの調査に時間を使ってしまいました…

Vue っぽくないやり方で回避

結局生のjavascript っぽい書き方で活性非活性を操ることにしました。
一番の親に当たるApp.vueではできたのに、子のMainPanel.vue ではリアクティブにならない…

今回はスキルアップが目的では無く、実装&体験の提供が主目的なので、
window.document.getElementById('push-btn-1').setAttribute('disabled', 'disabled')
window.document.getElementById('push-btn-1').removeAttribute('disabled')
で切り替えを実装しました。

ん〜〜〜〜モヤモヤぁーーー!!!!

解消方法

コメント欄に頂いた@knaito-531 さんからのご指摘で「アロー関数」が原因ではないかとのこと。

▼以下の箇所を…

<script>
methods: {
    pushedBtn: (s) => {
      if(s === 1){
      :
      :
      :
    }
</script>

▼以下の様に修正しました。

<script>
methods: {
    pushedBtn (s) {
      if(s === 1){
        :
        :
        :
    }
</script>

ご指摘頂いた通り、アロー関数をやめてみたところ、
リアクティブにボタンの活性非活性が変化する様になりました!!

参考

(勝手にリンクしてます。不都合ある方はお手数ですがおしらせください)

・ボタンの活性非活性切り替え (やり方間違ってたっけ?と思い答え合わせに拝見しました)
Vue.jsでボタンをdisabledして押せなくする方法

・data プロパティ変更の検知
 (概念のおさらい。そもそも関数がundifinedになるため適用できず…)
dataプロパティに登録したオブジェクトの変更を検出する方法

・概念のおさらい
リアクティブの探求 — Vue.js

14
10
4

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
14
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?