LoginSignup
0
0

More than 1 year has passed since last update.

Vue v-slot での select が無効化されている問題解決

Posted at

はじめに

Vue.js でのフロントのスプレッドシート画面を開発する際に、cell 内の select が無効化されていることが大きな壁になりました。その課題は Vue.js での親コンポーネントの v-slot 部分に子コンポーネントの scopeId が継承できないという共通の課題であり、本文ではその課題の原因の考察と解決法を共有します。

Vue画面の仕組み

<template>
  <app-layout>
      <v-spread
          v-model="dataset"
          :fields="fields"
          nameKey="apiName"
          :cellClass="cellClass"
          :cellReadonly="cellReadonly"
          ref="vspread"
          @mounted="onload"
      >
        <template v-slot:input="{field, item}">
          <div>
            <select v-model="item[field.apiName]">
              <option disabled value="">選択して下さい</option>
              <option v-for="option in options" :value="option.id">
                {{ option.name }}
              </option>
            </select>
          </div>
        </template>
      </v-spread>
  </app-layout>
</template>

<script type="text/javascript" defer=true>
import AppLayout from '@/Layouts/AppLayout'
import VSpread from '@/Shared/VueSpread'

export default {
  components: {
    AppLayout,
    VSpread
  },
}
</script>

<style scoped>

</style>

以上はメイン画面の一部です。メイン画面では VSpread という子コンポーネントをインポートしています、template v-slot を利用し、子コンポーネントの cell 内に select を適用している仕組みになります。以上のコードで、メイン画面でスプレッドシートの cell 内選択ボックスが機能していると期待していますが、実際に選択ボックスが無効化されていることが判明しました。なぜそのようなことが起きましたのか、次は HTML element について考察してみます。

HTML elementの考察

<form id="form" data-v-40046a6d="">
    <div data-v-b1b3ea5c="">
        <select class="select" data-v-b1b3ea5c="">
            <option disabled="" value="" data-v-b1b3ea5c="">選択して下さい</option>
            <option value="1" data-v-b1b3ea5c="">1</option>
            <option value="2" data-v-b1b3ea5c="">2</option>
            <option value="3" data-v-b1b3ea5c="">3</option>
        </select>
    </div>
</form>

以上は HTML element の一部です。form は子コンポーネントに所属する element、select は親コンポーネントに所属する element 、以上のコードで、select element のscopeId と form element の scopeId が違うことが判明しました。その原因で select element が子コンポーネント VSpread に定義されている CSS style が適用されてなく、メイン画面で操作できなくなることが分かりました。では、なぜ template v-slot の部分に子コンポーネントの scopeId を継承できないだろうか。それを解明するために、 Vue の Scope CSS のメカニズムについて解説します。

vue-loader について

全ての.vue ファイルに対して、最初は vue-loader によって処理されます。スコープ CSS に対して、簡単にまとめると、vue-loader は次の3つのことを行います。

  • コンポーネントを分析し、template、script、style に対応しているコードを抽出します。
  • コンポーネントのインスタンスを構築し、インスタンスに scopeId をバインドします。
  • scopeId を使用してセレクターの属性に設定し、style の CSS コードをコンパイルします。

vue-loader から scopeId の生成する方法が分かったことで、scopeId の継承とどんな関係があるだろうか?

scopeId の継承順

みんな分かっていることと思いますが、Vue の Scope CSS に対して、子コンポーネントの root element は必ず親コンポーネントから scopeId を継承するという仕組みがあります。この点から、上記のサンプルに戻ると、.vue ファイルはメイン画面の.vue ファイル -> 子コンポーネントの VueSpread.vue という順番で解析されることが分かりました。
つまり、vue-loader がメイン画面の.vue ファイルをコンパイルする時点では、子コンポーネントはまだ解析されていない、子コンポーネントの scopeId はまだ生成されていない状態なので、メイン画面での template v-slot に子コンポーネントの scopeId を渡すには不可能なことである。
自動的に scopeId を追加できないことが判明したことで、問題解決するためには手動で select に scopeId を追加しなければなりません。ここでは2つの方法を紹介します。

setAttribute で scopeId 属性を追加。

scopeId 属性を追加する前に、先ずは子コンポーネントの scopeId を取得しなければなりません。ここでは子コンポーネントの mounted() に emit を追加する方法で取得します。mounted() と emit について具体的にはここに参照してください。

<template>
  <app-layout>
      <v-spread
          v-model="dataset"
          :fields="fields"
          nameKey="apiName"
          :cellClass="cellClass"
          :cellReadonly="cellReadonly"
          ref="vspread"
          @mounted="onload"
      >
          <select ref="myselect" v-model="item[field.apiName]"/>

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

<script type="text/javascript" defer=true>
import AppLayout from '@/Layouts/AppLayout'
import VSpread from '@/Shared/VueSpread'

export default {
  components: {
    AppLayout,
    VSpread
  },
  data: () => ({
    scopeID: '',
  }),
  methods: {
    onload() {
      let vspread = this.$refs.vspread;
      let app = vspread.$refs.app
      this.scopeID = app.attributes[1].name
      let select = this.$refs.myselect
      select.setAttribute(this.ScopeID,'')
    },
  }
}
</script>

<style scoped>

</style>

以上のような子コンポーネントが mounted した時点でトリガーを送信し、onload() メソッドで attributes[1].name で子コンポーネントの scopeID を取得し、 select.setAttribute で select に子コンポーネントの scopeID を追加します。

動的属性で scopeID を追加

Vue のライフサイクルの関係で、子コンポーネントの mounted 完成した時点でも親コンポーネントでは、特定な DOM element はまだ作成されていない可能性があるので、その場合 setAttribute が使えなくなり、その時に対応できる方法としては動的属性で scopeID を追加することです。

<template>
  <app-layout>
      <v-spread
          v-model="dataset"
          :fields="fields"
          nameKey="apiName"
          :cellClass="cellClass"
          :cellReadonly="cellReadonly"
          ref="vspread"
          @mounted="onload"
      >
        <template v-slot:input="{field, item}">
          <div>
            <select v-bind:[scopeID]="scopeID" v-model="item[field.apiName]">
              <option disabled value="">選択して下さい</option>
              <option v-for="option in options" :value="option.id">
                {{ option.name }}
              </option>
            </select>
          </div>
        </template>
      </v-spread>
  </app-layout>
</template>

<script type="text/javascript" defer=true>
import AppLayout from '@/Layouts/AppLayout'
import VSpread from '@/Shared/VueSpread'

export default {
  components: {
    AppLayout,
    VSpread
  },
  data: () => ({
    scopeID: '',
  }),
  methods: {
    onload() {
      let vspread = this.$refs.vspread;
      let app = vspread.$refs.app
      this.scopeID = app.attributes[1].name
    },
  }
}
</script>

<style scoped>

</style>

上と同じような方法で子コンポーネントの scopeID を取得しています、こちらは select に v-bind:[scopeID]="scopeID" で動的属性 scopeID を設定しています。

0
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
0
0