20201028のvue.jsに関する記事は15件です。

【Vue.js】window.confirmを自前で作る

d1.png
window.confirmと似た使用感の確認ダイアログを自作しました。


確認バージョン

  • vue: 2.6.11
  • vuetify: 2.3.2


使用例

以下のように、コンポーネントごとにmixinを読み込んで使います。
文字列の配列を渡すと、複数行のメッセージとして表示される仕組みになっています。

@/components/MyComponent.vue
<template>
  <div class="container">
    <h2>氏名変更</h2>
    <v-btn small class="button primary" @click="changeName">実行</v-btn>
  </div>
</template>
<script lang="ts">
import Vue from 'vue';
import ConfirmDialogMixin from '@/mixins/ConfirmDialogMixin.ts';

export default Vue.extend({
  mixins: [ConfirmDialogMixin],
  data() {
    return {
      userAccountBefore: {jpName: '田中 一郎', enName: 'Tnaka Ichiro'},
      userAccountAfter: {jpName: '田中 二郎', enName: 'Tnaka Jiro'},
    };
  },
  methods: {
    async changeName(): Promise<void> {
      // こんな感じで呼び出す
      const isUserClickOK = await this.mixinConfirm([
        '以下の通り変更します。よろしいでしょうか?',
        '■氏名',
        `変更前: ${this.userAccountBefore.jpName}`,
        `変更後: ${this.userAccountAfter.jpName}`,
        '■English Name',
        `変更前: ${this.userAccountBefore.enName}`,
        `変更後: ${this.userAccountAfter.enName}`,
      ]);
      if (isUserClickOK) {
        // OKを押した時の処理
      }
    },
  },
});
</script>
<style scoped lang="scss">
.container {
  padding: 20px;
}
</style>

q2.png


confirm部分のコード

ConfirmDialogMixin.tsにて非グローバルのmixinを定義。
mixin関数を呼ぶとConfirmDialog.vueのインスタンスを作成し、OK/キャンセルを押すと破棄する作りです。

@/mixins/ConfirmDialogMixin.ts
import Vue from 'vue';
import vuetify from '@/plugins/vuetify';
import ConfirmDialog from '@/components/generalParts/ConfirmDialog.vue';

declare module 'vue/types/vue' {
  interface Vue {
    mixinConfirm(messages: string[]): Promise<boolean>;
  }
}

export default {
  methods: {
    // window.confirm()と似た感じで使うconfirm用の共通関数
    mixinConfirm: (messages: string[]): Promise<boolean> => {
      return new Promise((resolve) => {
        const VM = Vue.extend(ConfirmDialog);
        new VM({
          vuetify, // <- これをつけなくても動くが、consoleエラーが出まくる
          propsData: {
            messages,
            onClickOK: () => {
              return resolve(true);
            },
            onClickClose: () => {
              return resolve(false);
            },
          },
        });
      });
    },
  },
};
@/components/generalParts/ConfirmDialog.vue
<template>
  <v-dialog :value="isDialogActive" max-width="600" persistent>
    <div class="container">
      <div class="messages">
        <div v-for="(message, index) in messages" :key="index">
          {{ message }}
        </div>
      </div>
      <v-btn small class="button primary" @click="ok">OK</v-btn>
      <v-btn small class="button secondary" @click="cancel">キャンセル</v-btn>
    </div>
  </v-dialog>
</template>

<script lang="ts">
import Vue, { PropType } from 'vue';

export default Vue.extend({
  props: {
    messages: {
      type: Array as PropType<string[]>,
      required: true,
    },
    onClickOK: {
      type: Function,
      required: true,
    },
    onClickClose: {
      type: Function,
      required: true,
    },
  },
  data() {
    return {
      isDialogActive: false,
    };
  },
  created() {
    this.$mount();
    document.body.appendChild(this.$el);
    this.isDialogActive = true;
  },
  methods: {
    ok() {
      this.onClickOK();
      this.close();
    },
    cancel() {
      this.onClickClose();
      this.close();
    },
    close() {
      this.isDialogActive = false;
      // アニメーションが見えるよう若干待つ
      setTimeout(() => {
        if (document.body.contains(this.$el)) {
          document.body.removeChild(this.$el);
        }
        this.$destroy();
      }, 200);
    },
  },
});
</script>

<style lang="scss" scoped>
$spacingNormal: 8px;
.container {
  border: solid 2px;
  border-radius: 4px;
  background-color: #F2F7F2;
  padding: $spacingNormal;
}
.messages {
  max-height: 300px; // メッセージが長くてもこの高さが最大
  overflow: scroll;  // はみ出た分はスクロールさせる
  border-radius: 4px;
  background-color: #FFFFFF;
  padding: $spacingNormal;
  line-height: 1.25;
}
.button {
  width: 120px;
  margin-top: $spacingNormal;
  margin-right: $spacingNormal;
}
</style>


vuetifyを使えるようにするコード

一応、vuetifyを使えるようにするコードも載せておきます。
main.tsと別ファイルに書くことで、main/test/mixinで同じ定義を使える利点があります。

@plugin/vuetify.ts
import Vue from 'vue';
import Vuetify from 'vuetify';
import 'vuetify/dist/vuetify.min.css';

Vue.use(Vuetify);

export default new Vuetify({
  theme: {
    dark: false,
    themes: {
      light: {
        primary: '#EA9034',
        secondary: '#564C46',
      },
    },
  },
});
main.ts
import Vue from 'vue';
import App from './App.vue';
import router from './router';

import vuetify from './plugins/vuetify';

new Vue({
  router,
  vuetify,
  render: (h) => {
    return h(App);
  },
}).$mount('#app');


実装意図

  • なぜ自作のconfirmを作ったの?
    • ページ全体のデザインを統一したかったから
  • なぜvuetifyを使ったの?
    • デザイン周りの実装を最低限にしたかったから
  • なぜ文字列の配列を渡すことにしたの? テンプレートリテラルではだめなの?
    • コードの見た目が、文章の見た目と近くなるから
    • 他コードとインデントを揃えられるから
      (テンプレートリテラルで複数行メッセージを書く場合、インデントしにくいのが嫌だった)
    • 繰り返し要素に強いから
      (mixinConfirm(dataArray.map((data) => {return toMessage(data)})みたいにできる)
  • なぜグローバルミックスインにしないの?
    • 依存関係を明示的にしたかったから
    • mixinの乱用を避けたかったから
      (mixinはアンチパターンという意見が多い)


参考

https://qiita.com/totto357/items/6e5df072fdb0ccbe8c51
https://zukucode.com/2020/04/vue-alert-confirm.html
技術的には上記2記事の完全なパクリです。
2例を組み合わせ、好みの感じにチューニングしてみた、という記事です。
先人に感謝。


最後に

はやくvuetifyがvue3に対応しますように。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VuetifyのCSSを上書きしたいときに取りうる選択肢

VuetifyのCSSは強い

基本的にいじらなくてもいい感じにきれいにできるという意味と、overrideしようとすると若干面倒くさいという二重の意味で強いVuetifyのCSSですが、変更したい場合があり思うところがあったので、こんなときにはこの方針でという備忘録

そのコンポーネントでしか使わない

scoped styleを使用して、あげれば事足りる。要素に対して直接Styleを付けなければここが最後に読み込まれるので、あるコンポーネントでしか使わないようなクラスがあればここに書いてあげればそれでいい

<style scoped lang="sass">
.wrap-text
  word-break: break-all
  white-space: normal
</style>

複数のVuetify標準コンポーネントに適応したい

この場合都度Scoped styleに持ってくるのは面倒なのでグローバルで呼べるようにする。しかしこの場合には、単純にクラスを追加するだけだとVuetifyの標準CSSの方が優先されることがあるので、確実にoverrideしたい場合には、キチンとネストして書くかdeepを使用するか、最終奥義 !importantを使用する。

components/_sample.sass
.wrap-text
  word-break: break-all !important
  white-space: normal !important
app.sass
@import './components/_sample'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】tableの要素を選択可能にする方法。クリックに合わせclass属性を付与する

【Vue】tableの要素を選択可能にする方法。クリックに合わせclass属性を付与する

v-forとv-ifで作成した表の各要素を選択可能にする。

選択した場合は枠を青色にし、選択を解除した場合は元の色に戻す。



▼最終的なアウトプット例
セルの複数選択を可能にする。
image.png

前提として、v-forとv-ifを使った表の作成はこちらを参照。

目次

  1. 選択状態の考え方
  2. 使用する主な機能
    1. クラス属性の付け外し
    2. クリックイベント
    3. $event
    4. findIndexメソッド
    5. sliceメソッド
    6. スプレッド構造
  3. 選択可能の実例(1つのセルのみの場合)
  4. 複数選択


選択状態の考え方

要素をクリックした際に、クリックイベントで選択中のセルに選択中を表す属性の有無を判定し、属性があればそれを外し、なければ付与する。

クリックしたセルがどの要素かを判断するには配列番号を使う。

▼配列番号によるセルの識別イメージ

0,0 0,1 0,2
1,0 1,1 1,2
2,0 2,1 2,2

例えば、左上のセルをクリックした場合は、列番号0、行番号0のセルに属性を付与する操作となる。


使用する主な機能

クラス属性の付け外し

クラス属性にv-onを使うことで、状態に合わせクラス属性を付与したり外したりすることができる。

:class="{'付与するクラス名': 真偽値"}

真偽値は、booleanとなる変数でも条件式をいれても動く。


クリックイベント

要素に対して以下の属性を設定する。

@click="イベント名(引数)"

要素がクリックされると指定したイベントが発火する。
イベントはmethodsオプションの中に記述する。

  methods:{
    イベント名(引数) {
      処理
    },


$event

イベントオブジェクト。
clickなど、イベントが発生した際に、イベントの詳細情報をごっそり持ったデータ。

イベントが発生した画面上の位置や、イベントが発生した要素、その親要素の情報が入っている。

$event.target: イベントが発生した要素(タグ)
$event.target.parentNode: イベントが発生した要素の親要素
$event.type: 発生したイベント名 (clickなど)
$event.pageX: イベントが発生したx座標の値

今回は、targetとtarget.parentNodeを使用する。


findIndexメソッド

JSのメソッドであるfindIndexで要素のインデックス番号を取得する。

findIndexは指定した配列の要素をfor文で一つづつ取り出し、条件と一致した要素があればその配列番号を返すメソッド。

arr.findIndex((変数)=>(条件式))
 ┗ 変数には一つづつ取り出したarrの要素が入る
 ┗ 条件式に一致したら処理終了。インデックス番号を返す
 ┗ 指定した条件が見つからない場合は「-1」を返す


sliceメソッド

JSのメソッドであるsliceを活用して、指定した値を削除する。

slice自体は指定した範囲を指定して要素を抜き出すメソッド。

arr.slice(開始番号,終了番号)
 ┗ 開始番号から、終了番号の手前までの要素を抜き出す。
 ┗ 終了番号の要素は含まない。
 ┗ 終了番号がない場合は、配列の最後の用をまでを表示

sliceは非破壊的なため、元の配列はそのまま。
元の配列も変更する場合は代入が必要。


スプレッド構造

配列の値を結合する際にスプレッド構文を使用する。

スプレッド構文とは、配列の前に「...」をつけることで、配列のカッコ([ ])を外す方法。

配列同士を結合する場合に、それぞれにスプレッド構文を付けて、カッコ([ ])で囲むと結合できる。

スプレッド構文
a = [1,2]
b = [3,4]

console.log(...a)
//1 2

console.log([...a, ...b])
//[1,2,3,4]

sliceとスプレッド構文を用いた配列の削除方法についてはこちらを参照


1. 選択可能の実例(1つのセルのみの場合)

セルをクリックするごとに青枠が移動する。

▼完成イメージ
image.png

htmlの設定
      <template v-for="(tr, rowIndex) in rows">
        <tr :key="rowIndex">
          <template v-for="(cell, cellIndex) in tr.table_cells">
            <th :key="cellIndex" 
                v-if="cell.cell_type == 'TH'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                @click="clickCell($event)">
              <br>
            </th>

<tr :key="rowIndex">
v-forで取り出したtrタグのインデックス番号を変数rowIndexに格納する。

<th :key="cellIndex"
v-forで取り出したthタグのインデックス番号を変数cellIndexに格納する。

:class="{'is-active': isActive(rowIndex, cellIndex)}"
変数isActiveに、変数rowIndexとcellIndexを渡したときに、出力がtrueならクラスis-activeを付与する。falseならis-activeを外す。

@click="clickCell($event)"
要素がクリックされたら、clickCellイベントを発動する。
引数で$eventを渡す。


methods
  methods:{
    clickCell(event){
      const cell = event.target
      const tr = event.target.parentNode

      this.currentCell = {
        xxxrowIndex: tr.rowIndex,
        xxxcellIndex: cell.cellIndex
      }
    },
    isActive(rowIndex, cellIndex){
      return this.currentCell.xxxrowIndex == rowIndex && this.currentCell.xxxcellIndex == cellIndex
    }
  }

const cell = event.target
$eventオブジェクトを引数eventとして渡し、targetプロパティを変数cellに代入する。

イベントが発生したセルの情報を変数セルに格納する。
このタグ中に格納されているcellIndexの取得目的。

const tr = event.target.parentNode
クリックした要素の親要素を変数trに代入する。
このタグ中に格納されているrowIndexの取得目的。

xxxrowIndex: tr.rowIndex
変数currentCell内に、xxxrowIndexを定義し、現在のセルの行番号を格納する。

xxxを付けているのは、trの中のrowIndexと区別するため。



return this.currentCell.xxxrowIndex == rowIndex
変数currentCell内に格納されているxxxrowIndexの値と、for文で個別に取り出しているrowIndexの値が一致すれば、trueを返す。

scss
<style lang="scss" scoped>
table{
  width: 80%;
  th,td{
    border: thin solid rgba(0, 0, 0, 0.12);
  }
  th{
    background: #ccc;
  }
  th, td{
    //選択状態
    &.is-active{
      border: 1px double #0098f7;
    }
  }
}
</style>

&.is-active
「&」は親のセレクタを表す。

「th.is-active」「td.is-active」と同じになる。


▼コード(full)

fullコード
<template>
  <div>
    <table>
      <template v-for="(tr, rowIndex) in rows">
        <tr :key="rowIndex">
          <template v-for="(cell, cellIndex) in tr.table_cells">
            <th :key="cellIndex" 
                v-if="cell.cell_type == 'TH'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                @click="clickCell($event)">
              <br>
            </th>

            <td :key="cell.index" 
                v-else-if="cell.cell_type == 'TD'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                @click="clickCell($event)">
              <br>
            </td>
          </template>
        </tr>
      </template>
    </table>

  </div>
</template>

<script>
export default {
  data(){
    return{
      currentCell:{},
      rows: [
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
      ]
    }
  },
  methods:{
    clickCell(event){
      const cell = event.target
      const tr = event.target.parentNode

      this.currentCell = {
        xxxrowIndex: tr.rowIndex,
        xxxcellIndex: cell.cellIndex
      }
    },
    isActive(rowIndex, cellIndex){
      return this.currentCell.xxxrowIndex == rowIndex && this.currentCell.xxxcellIndex == cellIndex
    }
  }
}
</script>

<style lang="scss" scoped>
table{
  width: 80%;
  th,td{
    border: thin solid rgba(0, 0, 0, 0.12);
  }
  th{
    background: #ccc;
  }
  th, td{
    //選択状態
    &.is-active{
      border: 1px double #0098f7;
    }
  }
}
</style>

・現在選択中のセルの行列番号を格納する変数 currentCell を設ける。

・クリックイベントが発生したら、そのセル行列番号をcurrentCellにそれぞれ格納する。

・currentCellの行列番号と現在選択中の行列番号が一致すればtrueを返す。


2. 複数選択

先ほどは選択可能な要素は1つのみだったが、複数選択する場合を考えてみる。

▼完成イメージ
image.png

単一選択では現在選択中のセルを格納する変数に1つの要素のみを入れたが、今回は複数のセル情報を格納する。

  data(){
    return{
      currentCells:[],

配列currentCellsを用意して、値を {} から [] に変更する。


class属性の判定

class属性の判定
  methods:{
    //isActiveの判定
    //currentCellsの中にあればtrueを返す
    //条件式は指定した行列番号の要素がある=数値が-1以外ならtrueにする
    isActive(rowIndex, cellIndex){
      return this.currentCells.findIndex((elem) =>
        elem.xxxrowIndex == rowIndex && elem.xxxcellIndex == cellIndex
        ) > -1
    },

変数currentCellsに指定したrowIndexとcellIndexに該当する要素がないか判定する。

判定にはfindIndexメソッドを使用。

要素が含まれている場合はtrueを、ない場合はfalseを返す。



▼(参考)currentCellsの中身

以下のクリックイベントで設定するが、curretCellsの中身は以下のようになる。

currentCells
[ 
 { "xxxrowIndex": 0, "xxxcellIndex": 0 },
 { "xxxrowIndex": 1, "xxxcellIndex": 1 }, 
 { "xxxrowIndex": 2, "xxxcellIndex": 2 } 
]

↑以下の3つのセルを選択した場合。
image.png


クリックイベント

クリックイベント
methods:{
    clickCell(event){
      //クリックされたセルの情報
      const cell = event.target
      const tr = event.target.parentNode

      //クリックされたセルが既に選択されている場合は、配列から削除する
      if(this.isActive(tr.rowIndex, cell.cellIndex)){

        //選択中の配列の何番目の要素かを求める
        const rmIndex = this.currentCells.findIndex((elem)=>
          elem.xxxrowIndex == tr.rowIndex && elem.xxxcellIndex == cell.cellIndex 
        )

        //選択した要素を選択中の配列から削除する
        this.currentCells = [
          ...this.currentCells.slice(0, rmIndex),
          ...this.currentCells.slice(rmIndex + 1)
        ]

      } else{
        this.currentCells = [
          ...this.currentCells,
          {
            xxxrowIndex: tr.rowIndex,
            xxxcellIndex: cell.cellIndex
          }
        ]
      }
    },

選択中のクラス属性の有無で条件分岐を設定する。

▼isActiveがtrueの場合
クラス属性がある(currentCellsに含まれている)場合は、その要素を削除する。

要素の検出
        //選択中の配列の何番目の要素かを求める
        const rmIndex = this.currentCells.findIndex((elem)=>
          elem.xxxrowIndex == tr.rowIndex && elem.xxxcellIndex == cell.cellIndex 
        )

findIndexメソッドを使って、クリックした要素の行列番号に該当する要素の行列番号を取得する。



sliceメソッドとスプレッド構文を使って取得した要素を削除する。

要素の削除
        //選択した要素を選択中の配列から削除する
        this.currentCells = [
          ...this.currentCells.slice(0, rmIndex),
          ...this.currentCells.slice(rmIndex + 1)
        ]



▼isActiveがfalseの場合
現在選択中でない場合は、クリックした要素の行列番号を配列に追加する。

追加はスプレッド構文を使う。

要素の追加
else{
        this.currentCells = [
          ...this.currentCells,
          {
            xxxrowIndex: tr.rowIndex,
            xxxcellIndex: cell.cellIndex
          }
        ]
      }

以上の設定で複数が可能となる。

▼複数選択の例

image.png

image.png

セルを再度クリックすると選択が外れる。

コード(full)

fullコード
<template>
  <div>
    <p>〜TmpTrTd.vue〜</p>
    <p>{{currentCells}}</p>

    <table>
      <template v-for="(tr, rowIndex) in rows">
        <tr :key="rowIndex">
          <template v-for="(cell, cellIndex) in tr.table_cells">
            <th :key="cellIndex" 
                v-if="cell.cell_type == 'TH'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                @click="clickCell($event)">
              <br>
            </th>

            <td :key="cell.index" 
                v-else-if="cell.cell_type == 'TD'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                @click="clickCell($event)">
              <br>
            </td>
          </template>
        </tr>
      </template>
    </table>

  </div>
</template>

<script>
export default {
  data(){
    return{
      currentCells:[],
      rows: [
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
      ]
    }
  },
  methods:{
    //isActiveの判定
    //currentCellsの中にあればtrueにする
    //指定した行列番号の要素がある=数値が-1以外ならtrueにする。
    isActive(rowIndex, cellIndex){
      return this.currentCells.findIndex((elem) =>
        elem.xxxrowIndex == rowIndex && elem.xxxcellIndex == cellIndex
        ) > -1
    },

    clickCell(event){
      //クリックされたセルの情報
      const cell = event.target
      const tr = event.target.parentNode

      //クリックされたセルが既に選択されている場合は、配列から削除する
      if(this.isActive(tr.rowIndex, cell.cellIndex)){

        //選択中の配列の何番目の要素かを求める
        const rmIndex = this.currentCells.findIndex((elem)=>
          elem.xxxrowIndex == tr.rowIndex && elem.xxxcellIndex == cell.cellIndex 
        )

        //選択した要素を選択中の配列から削除する
        this.currentCells = [
          ...this.currentCells.slice(0, rmIndex),
          ...this.currentCells.slice(rmIndex + 1)
        ]

      } else{
        this.currentCells = [
          ...this.currentCells,
          {
            xxxrowIndex: tr.rowIndex,
            xxxcellIndex: cell.cellIndex
          }
        ]
      }
    },
  }
}
</script>

<style lang="scss" scoped>
table{
  width: 80%;
  th,td{
    border: thin solid rgba(0, 0, 0, 0.12);
  }
  th{
    background: #ccc;
  }
  th, td{
    //選択状態
    &.is-active{
      border: 1px double #0098f7;
    }
  }
}
</style>



以上。findIndexメソッド、sliceメソッド、スプレッド構文が鍵となる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】sliceメソッドを使って指定した配列番号の要素を削除する方法

sliceメソッドを使って指定した配列番号の要素を削除する方法

sliceを活用することで、指定した番号の要素を削除することができる。

pythonのpopメソッドは指定した配列番号を削除できるが、JSでは配列の末尾から削除する仕様となってしまう。

pythonのpopのように指定した配列番号の要素を削除する機能は、jsのsliceメソッドを使うことで実現できる。

sliceメソッド

arr.slice(開始番号,終了番号)
 ┗ 開始番号から、終了番号の手前までの要素を抜き出す。
 ┗ 終了番号の要素は含まない。
 ┗ 終了番号がない場合は、配列の最後の用をまでを表示

sliceは非破壊的なため、元の配列はそのまま。
元の配列も変更する場合は代入が必要。

arrs=["aaa","bbb","ccc","ddd","eee"]

arrs.slice(0, 2)

//出力
//["aaa", "bbb"]


//非破壊の確認
arrs
//出力
//["aaa","bbb","ccc","ddd","eee"]



▼終了番号を指定しない場合

arrs=["aaa","bbb","ccc","ddd","eee"]

arrs.slice(2)

//出力
//["ccc", "ddd", "eee"]

配列番号2以降の要素が抽出される。



▼元の値を置き換える
元の値に代入すれば、破壊的な処理にすることができる。

arrs=["aaa","bbb","ccc","ddd","eee"]

arrs = arrs.slice(3)
arrs

//出力
//["ddd", "eee"]


スプレッド構造

最初に、配列の値を結合する際に使用するスプレッド構文を確認しておく。

スプレッド構文とは、配列の前に「...」をつけることで、配列のカッコ([ ])を外す方法。

配列同士を結合する場合に、それぞれにスプレッド構文を付けて、カッコ([ ])で囲むと結合できる。

スプレッド構文
a = [1,2]
b = [3,4]

console.log(...a)
//1 2

console.log([...a, ...b])
//[1,2,3,4]



ちなみに、スプレッド構文を使用しない場合は、望んだ形にならない。

a = [1,2]
b = [3,4]

console.log(a+b)
//1,23,4


sliceを使って指定した要素を削除する

考え方は、(1)指定した配列番号の前までの値と(2)指定した配列番号より後ろの値を取得し、それぞれを結合する。

arrs = [ "aaa", "bbb", "ccc", "ddd", "eee" ]

//削除する配列番号
rmIndex = 2

//sliceを使った削除
arrs = [
  ...arrs.slice(0,rmIndex),
  ...arrs.slice(rmIndex+1)  
]

console.log(arrs)

//出力
//["aaa", "bbb", "ddd", "eee"]

指定した番号arrs[2]を削除することができた。
削除する番号を動的に指定すれば、指定した要素を消すことができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Vue.js 】Async/awaitとaxiosによる非同期処理

methodsに記述したaxiosによるHTTP通信の完了を待ってから残りの処理をしたい必要があったのですが、少し詰まったので記録として残しておきます。
※Async/awaitやaxiosについての説明記事ではありませんのでご了承ください

  mounted: {
    this.settingXxx();
  },
  methods: {
    fetchSample: async function(){
      let ret = null
      // 非同期処理を記述
      await axios.get('(url)', {
        .then((response) => {
          ret = response
        })
        .catch((error) => {
          this.errorMsg = 'Error! Could not reach the API. ' + error
          console.log(this.errorMsg)
        })
      return ret
    },
    settingXxx: async function(){
      // this.fetchSample()の実行が完了するまで待機
      let result = await this.fetchSample()
      console.log(result) //待機後の残りの処理を記述
    },
  }

fetchSample();によりPromiseオブジェクトが返され,
Promiseの状態が確定しその結果が返されるまで、JavaScriptを待機させます。

参考にさせていただいた記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Vue.js 】(簡易メモ) Async/awaitとaxiosによる非同期処理

methodsに記述したaxiosによるHTTP通信の完了を待ってから残りの処理をしたい必要があったのですが、少し詰まったので記録として残しておきます。
※Async/awaitやaxiosについての説明記事ではありませんのでご了承ください

  mounted: {
    this.settingXxx();
  },
  methods: {
    fetchSample: async function(){
      let ret = null
      // 非同期処理を記述
      await axios.get('(url)', {
        .then((response) => {
          ret = response
        })
        .catch((error) => {
          this.errorMsg = 'Error! Could not reach the API. ' + error
          console.log(this.errorMsg)
        })
      return ret
    },
    settingXxx: async function(){
      // this.fetchSample()の実行が完了するまで待機
      let result = await this.fetchSample()
      console.log(result) //待機後の残りの処理を記述
    },
  }

fetchSample();によりPromiseオブジェクトが返され,
Promiseの状態が確定しその結果が返されるまで、JavaScriptを待機させます。

参考にさせていただいた記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue.jsを初めて学ぶ ③ [レンダリング]

前回へのリンク

vue.jsを初めて学ぶ ① [hello world]
vue.jsを初めて学ぶ ② [テンプレート構文]


目次

1. v-ifを使用した、条件付きレンダリング

2. v-forを使用した、リストレンダリング


条件付きレンダリング

1. v-if

要素を消す
切り替えが高頻度だと、処理が重くなる
要素を削除しているため

切り替えが頻繁ではない時は、v-if

2. v-show

cssベースで消す

初期表示が遅い
表示してからdisplay noneしているため

とても頻繁に切り替える時は、v-show


リストレンダリング

1. v-forとは

  • リストレンダリングを可能にする。

2. 配列から取り出す

index.html
  <div id = "app">
    <ul>
      <li v-for="fruit in fruits">
        {{fruit}} 
      </li>
    </ul>
  </div>
  1. v-for の使い方
    (引数) in 配列名
  2. {{ }} 二重括弧内に引数を入れる。
  3. index, value の2つの要素を持っていることをチェック。
index.js
    new Vue ({
      el: '#app',
      data: {
        fruits: ['りんご','ばなな','チェリー']
      }
    })
  1. 変数fruitsに代入。
    配列形式で{value}を作成。

3. オブジェクトから取り出す

index.html
<div id = "app">
    <ul>
      <li v-for="(value,key,index) in object"> 
        {{index}}{{key}}{{value}}
      </li>
    </ul>
  </div>
  1. v-for の使い方
    (引数) in オブジェクト名
  2. {{ }} 二重括弧内に引数を入れる。
  3. key, value, index の3つの要素を持っていることをチェック。
index.js
    new Vue ({
      el: '#app',
      data: {
        object: {
          firstName: '太郎',
          lastName: '田中',
          age: 21
        }
      }
    })
  1. 変数objectに代入。
    オブジェクト形式で{key: value}を作成。

4. タグを利用する

  • タグより、要素が残らないタグを使うとスッキリ。
    index.htmlタグ使用
      <div id = "app">
        <ul>
          <template v-for="(value, index) in fruits">
            <li>
                ({{index}}){{value}}
            </li>
            <hr>
          </template>
        </ul>
      </div>
    

    スクリーンショット 2020-10-28 15.15.28.png

    index.html
    タグ使用
      <div id = "app">
        <ul>
          <div v-for="(value, index) in fruits">
            <li>
                ({{index}}){{value}}
            </li>
            <hr>
          </div>
        </ul>
      </div>
    

    スクリーンショット 2020-10-28 15.14.27.png

    div タグより、スッキリして見えますね!

    5. inとofの分類

    ofは、javascriptのイテレーター構文
    ofが使われていても間違ってはいない!
    Vue.jsでは、基本的にinを使用する

    6. v-forレンダリング時に、必須のキー属性

    • v-forのリストレンダリング時、
      必ずキー属性を使用

    • キー属性とは、templateタグで使用できない

    • v-forは、
      要素の移動を最小限に抑えるアルゴリズムを使用し可能な限り同じタイプの要素を再利用しようとする性質があるから。

    index.html
      <div id = "app">
        <ul>
          <div v-for="fruit in fruits">
            <p>{{fruit}}</p>
            <input type="text">
          </div>
        </ul>
        <button v-on:click="remove">先頭削除</button>
      </div>
    
    index.js
        new Vue ({
          el: '#app',
          data: {
            fruits: ['いちご','ばなな','キウィ']
          },
          methods: {
            remove: function() {
              this.fruits.shift()
            }
          }
        })
    
    1. 3つのテキストボックス内に入力し、先頭削除ボタンをクリック。
    2. 先頭が削除された時、テキストボックス内の文字列がずれてしまう。
    3. これはVue.jsが効率の良いアルゴリズムでレンダリングした結果。
    4. 要するに効率を優先する事で、ズレが起きてしまう

    どうすれば良い??

    7. レンダリング時、キー属性を指定する!

    index.html
      <div id = "app">
        <ul>
          <div v-for="fruit in fruits" key="fruit">
            <p>{{fruit}}</p>
            <input type="text">
          </div>
        </ul>
        <button v-on:click="remove">先頭削除</button>
      </div>
    
    index.js
        new Vue ({
          el: '#app',
          data: {
            fruits: ['いちご','ばなな','キウィ']
          },
          methods: {
            remove: function() {
              this.fruits.shift()
            }
          }
        })
    

    keyとして、毎回レンダリングで代入される変数fruitを指定

    keyとして、indexは指定できない。ズレが生じる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Computed property は、あくまでデータの一種

computedプロパティーは、データなのかメソッドなのか、いまいちわからない。

ただ、実装上は、あくまでデータとして扱うのが良いと学んだ。

Vueのオプションは、いまいち数が多くて使い分けがわからない。
しかし、

  1. いつ(ライフサイクルフック)
  2. 何を(データ)
  3. どうする(メソッド)

に分けて考えるとわかりやすいと思う。

データについては、
1. props  親からもらった他人のデータ
2. data   自前のデータ
3. computed 加工済みの自前データ

の3つに分けて考える必要がある。

dataのプロパティをcomputedでいじろうとか思ってはいけない。

メソッドについては、
1. methods
の一択。

ライフサイクルフックは、
1. created
2. mounted
3. beforeDestroy
が主。

この場合は、例えば、
1. created()のタイミングで
2. propsを
3. methodsで加工する

というふうに分けて考えると迷子にならない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】v-forを使ってtableを表示する方法。td、thタグの出し分け

【Vue】v-forを使ってtableを表示する方法。td、thタグの出し分け

tableを作成する際に、要素の追加削除を簡単にやりたい。

まずは、tableの構造を理解する必要があるため、基礎的にv-forを使ってtableを作成する方法を学ぶ。


目次

  1. table作成の概念
  2. v-forを使ったtableの作成
  3. tdタグとthタグの出し分け

最終的にはこんな表(↓)が作れる。
image.png

table作成の概念

tableの概念の確認として、3x3の表を例として考える。

要素を分解すると、まずは1行に分けることができる。
この一塊がtrタグになる。

trタグの中身はセルが3つあるため、trタグの中にtdタグが3つあることになる。

つまり、trタグとなる配列を用意して、その中にセルの数分だけ要素を持たせればいい。

あとは、v-forでtrタグを一つづつ取り出し、さらに中のセル要素もv-forで取り出すとtableができる。



▼1行3列の配列のイメージ
tr用のデータを1つ用意し、その中に3つの要素を持たせる。

1行(セル3つ)
        {
          "table_cells": [
            {},
            {},
            {},
          ]
        },



▼3行3列の配列のイメージ
上記の1行を3回重ねて一つの配列にする。(ここではrowsに格納)

コード
rows: [
        {
          "table_cells": [
            {},
            {},
            {},
          ]
        },
        {
          "table_cells": [
            {},
            {},
            {},
          ]
        },
        {
          "table_cells": [
            {},
            {},
            {},
          ]
        },
      ]
    }


v-forを使ったtableの作成

先ほど作成したtableの元となる配列をv-forで取り出す。

※v-forの注意点

  • v-for使用時はkeyが必要。
  • templateに対してv-forを使う場合は直下のタグにkeyを設置
    <table>
      <template v-for="tr in rows">
        <tr :key="tr.index">
          <template v-for="cell in tr.table_cells">
            <td :key="cell.index">
              <input type="text">
            </td>
          </template>
        </tr>
      </template>
    </table>

<template v-for="tr in rows">
trタグとなる要素を取り出す。

<template v-for="cell in tr.table_cells">
取り出したtr要素の中でセルとなる要素を取り出す

<input type="text">
セルの中身。任意。ここでは入力できるようにしている。



▼画面表示
image.png

無事3x3の表を表示することに成功。

コード(full)
<template>
  <div>
    <table>
      <template v-for="tr in rows">
        <tr :key="tr.index">
          <template v-for="cell in tr.table_cells">
            <td :key="cell.index">
              <input type="text">
            </td>
          </template>
        </tr>
      </template>
    </table>

  </div>
</template>

<script>
export default {
  data(){
    return{
      rows: [
        {
          "table_cells": [
            {},
            {},
            {},
          ]
        },
        {
          "table_cells": [
            {},
            {},
            {},
          ]
        },
        {
          "table_cells": [
            {},
            {},
            {},
          ]
        },
      ]
    }
  }
}
</script>

<style lang="scss" scoped>
table{
  width: 80%;
  td{
    border: thin solid rgba(0, 0, 0, 0.12);
  }
}
</style>


tdタグとthタグの出し分け

応用編として、見出しのthタグと通常セルのtdタグの2パターンで出し分け、それぞれによって画面表示を変えてみる。

td, th出し分けの考え方

  • セルにtdかthを識別するプロパティを持たせる。
  • v-ifを使って、tdタグかthタグにするかを出し分ける。

実例

行の先頭をthタグ、残りをtdタグにする場合を考える。
trの中のセル要素をtdとtrタグのどちらに振り分けるかを判断するため、cell_typeというプロパティを設定する。

v-ifを使って、THの場合はthタグへ、TDの場合はtdタグへ振り分ける。

1行のイメージ
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },

v-forとv-ifの設定

v-ifとv-else-ifを使う

    <table>
      <template v-for="tr in rows">
        <tr :key="tr.index">
          <template v-for="cell in tr.table_cells">

            <!--THの場合はthタグにする-->
            <th :key="cell.index" v-if="cell.cell_type == 'TH'">
              <input type="text">
            </th>

            <!--TDの場合はtdタグにする-->          
            <td :key="cell.index" v-else-if="cell.cell_type == 'TD'">
              <input type="text">
            </td>
          </template>
        </tr>
      </template>
    </table>


スタイルの設定

thタグとtdタグの表示を分けるためCSSを設定。
thタグの背景色をグレー (#ccc)とする。

<style lang="scss" scoped>
table{
  width: 80%;
  th,td{
    border: thin solid rgba(0, 0, 0, 0.12);
  }
  th{
    background: #ccc;
  }
}
</style>


▼画面表示

image.png

狙い通り先頭のセルがthタグとなった。

fullコード
<template>
  <div>
    <table>
      <template v-for="tr in rows">
        <tr :key="tr.index">
          <template v-for="cell in tr.table_cells">
            <th :key="cell.index" v-if="cell.cell_type == 'TH'">
              <input type="text">
            </th>

            <td :key="cell.index" v-else-if="cell.cell_type == 'TD'">
              <input type="text">
            </td>
          </template>
        </tr>
      </template>
    </table>

  </div>
</template>

<script>
export default {
  data(){
    return{
      rows: [
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
      ]
    }
  }
}
</script>

<style lang="scss" scoped>
table{
  width: 80%;
  th,td{
    border: thin solid rgba(0, 0, 0, 0.12);
  }
  th{
    background: #ccc;
  }
}
</style>



▼補足
cell_typeの値をTHかTDで変更することでセルの色を簡単に変えることができる。

image.png

      rows: [
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TH"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TH"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TH"},
            {"cell_type": "TD"},
            {"cell_type": "TH"},
          ]
        },
      ]



以上。まだ実用にはほど遠いが、テーブルの基礎を抑えることができた。

テーブル奥深い、、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue3の始め方

はじめに

Vue.js 3.0リリースに併せて、Vue.jsをハンズオン形式で学習する書籍を出版しました。書籍では、Vue3への移行情報や新機能の中心になっているComposition APIを解説しています。書籍の宣伝を兼ねて、2020年10月時点のVue3の取り組み方について解説します。多大に宣伝を含みますが、ご了承ください。
最初に結論を。しばらくは公式ライブラリとUIコンポーネントライブラリの対応状況をウォッチし、Vue3の本格的な採用は様子見することを個人的におススメします。

Vue3の新機能

https://v3.vuejs.org/guide/migration/introduction.html#notable-new-features

Vue3の新機能をいくつか取り上げて紹介します。

Composition API

https://v3.vuejs.org/guide/composition-api-introduction.html

新しく導入されたコンポーネントの記述スタイルです。
次のプラグインを使うことで、Vue2でも一部の機能を除いて利用可能です。

書籍ではComposition APIを中心に解説しています。
Vue3と言えばComposition API、という感じでネット上にも情報が溢れていますのでここでは紹介を割愛します。

Teleport

https://v3.vuejs.org/guide/teleport.html#teleport

teleportタグで囲まれたコンテンツを、toで指定した場所に描画させることができる機能です。
公式ドキュメントのサンプルは次のとおりです。記述方法を確認してみてください。

template: `
  <button @click="modalOpen = true">
    Open full screen modal! (With teleport!)
  </button>

  <teleport to="body">
    <div v-if="modalOpen" class="modal">
      <div>
        I'm a teleported modal! 
        (My parent is "body")
        <button @click="modalOpen = false">
          Close
        </button>
      </div>
    </div>
  </teleport>
`,
template: `
  <h2>This is a parent component</h2>
  <teleport to="#endofbody">
    <child-component name="John" />
  </teleport>
`

Fragments

https://v3.vuejs.org/guide/migration/fragments.html

公式ドキュメントのサンプルを見ればわかると思いますが、templateタグ直下に複数のタグを並べることができるようになりました。

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

Breaking Changes

https://v3.vuejs.org/guide/migration/introduction.html#breaking-changes

Vue3のBreaking Changesをいくつか取り上げて紹介します。

Global API

インスタンスの生成やグローバルコンポーネントの登録など、グローバルAPIがインスタンスAPIに変更されています。
また、Vue.nextTickは次のようにインポートして使うなど、APIの見直しが行われています。

import { nextTick } from 'vue'

v-modelの変更

:value:modelValueに、@input@update:modelValueに変更されました。
v-bindディレクティブの.sync修飾子が廃止され、v-modelを使う形になります。
また、次の公式ドキュメントのサンプルのように、複数のv-modelを設定できるようになりました。

<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />

フィルターの廃止

メソッドで代用が効くため、Vue3で廃止されました。

$listenersの廃止

Vue3では、イベントリスナーは$attrsにマージされたため、$listenersはVue3で廃止されました。

.native修飾子の廃止

v-onディレクティブの.native修飾子が廃止されました。

Events API

$on$off$onceが廃止されました。

実験的な機能

次の機能が試験的に実装されています。

  • 単一ファイルコンポーネントの記述を簡潔にする機能
    SFC Composition API Syntax Sugar <script setup>
  • scriptタグ内から、styleタグに値を渡すことができる機能
    SFC State-driven CSS Variables <style vars>
    これによって、動的にスタイルを制御しやすくなります。
  • Suspenseコンポーネント
    コンポーネントを描画する準備が整っていないタイミングで、代替の表示を行うことができます。 ローディング処理の実装に使えそうです。

ライブラリの対応状況

https://github.com/vuejs/vue-next

Vue RouterがRCになりましたが、Vuexやその他周辺ライブラリはβの状態です。

UIコンポーネントライブラリの対応状況

UIコンポーネントライブラリのVue3対応は、もうしばらく待つことになりそうです。Vue3への移行は、実質UIコンポーネントライブラリの対応待ちになるでしょうね。
メジャーなライブラリのVue3への対応が言及されているIssueを示しておきます。

クラススタイルにどう向き合うか

主流はComposition APIを使った記述スタイルになっていくことが予想されますが、サーバーサイドエンジニアに馴染みの深い、オブジェクト指向言語に似た記述スタイルを捨てがたい人も居るのではないでしょうか。
Vue.jsの公式リポジトリ内では、Vue3へのクラススタイルの導入は一旦見送られていますが、クラススタイルをサポートするライブラリはそうとも限りません。次のモジュールを使っている場合は、ライブラリの対応をウォッチしておくのが良さそうです。

  • vue-property-decorator
  • vuex-module-decorators

Vue3への対応について言及しているIssueはこちらです。

まとめ

公式ライブラリやUIコンポーネントライブラリなどの対応状況をウォッチしながら、Vue3の学習をしつつ準備を進めるのが良さそうです。
既存のアプリケーションを移行するにもそれなりの作業量が予想されるため、少しずつ準備を進めましょう。プラグイン@vue/composition-apiを使った段階的な移行を検討するのもよいかもしれません。

書籍の紹介


書籍の情報サイト

紹介

人気のJavaScriptフレームワークVue.jsにフォーカスし、フロントエンド開発の基礎から本格的なSPAの開発まで、ハンズオン形式で一歩ずつ、無理なく着実にステップアップしていきます。 さらに、2020年リリース予定のVue.js 3.0をいち早くキャッチアップ。Vue CLI 4に対応しつつ、Vue.js 2.xとの差分として新しい記述スタイル(Composition API)を併記するなど、バージョン移行を強力に支援します。

基本情報

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

3つのFORMをJS→VUE.JSに書き換え

内容

 既にできているノーマルのJSをどうにかして、Vue.Jsに置き換えてみる練習
 単純に同じフォームが3つあるので、Vue.Jsでコンポーネントできるのではと思った
 簡略化のために、フォームのバリデーションは無し

ポイント

 FLASK側は、FLASK-FORMを使いフォームを名前で分岐している。
 Jinjaには、form1,form2,form3を渡している。

元のコード

{% extends "layout.html" %}
{% block body %}

<div id="content">
    <div class="container-fluid">
        <h1>データ1の読込</h1>
        <p>
            <form class="form form-horizontal" method="POST" action="{{url_for('data_import')}}"  enctype="multipart/form-data">
                {{ form1.hidden_tag() }}
                {{ form1.file }}
                <input type="submit" >
            </form>
        </p>
        <hr>
        <h1>データ2の読込</h1>
        <p>
            <form class="form form-horizontal" method="POST" action="{{url_for('data_import')}}"  enctype="multipart/form-data">
                {{ form2.hidden_tag() }}
                {{ form2.file }}
                <input type="submit" >
            </form>
        </p>
        <hr>

        <h1>データ3の読込</h1>
        <p>
            <form class="form form-horizontal" method="POST" action="{{url_for('data_import')}}"  enctype="multipart/form-data">
                {{ form3.hidden_tag() }}
                {{ form3.file }}
                <input type="submit" >
            </form>
        </p>
        <hr>
</div>
</div>

{% endblock %}

#データインポート処理
@app.route('/import',methods=["GET", "POST"])
def data_import():
    number_form = UploadForm()
    label_form = UploadForm()
    xxx_form = UploadForm()

    if request.method == "POST":
        if number_form.validate_on_submit():
            filename = secure_filename(number_form.file.data.filename)
            number_form.file.data.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
        elif label_form.validate_on_submit():
            filename = secure_filename(label_form.file.data.filename)
            label_form.file.data.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
        elif xxx_form.validate_on_submit():
            filename = secure_filename(label_form.file.data.filename)
            xxx_form.file.data.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))

        return redirect(url_for('data_import'))

    return render_template( 
        'import.html',form1=number_form,form2=label_form,form3=xxx_form)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue/require-v-for-keyエラーの発生原因と対処法

vue/require-v-for-keyエラーの発生原因と対処法

VScodeでv-for使用中に下記エラーが発生したので、その原因と対処法について。

[vue/require-v-for-key]
Elements in iteration expect to have 'v-bind:key' directives.eslint-plugin-vue

image.png

発生原因

・エラー通知はeslint-plugin-vueの機能による。
・v-for使用時はkey属性の使用が推奨されるため

あくまで推奨のサポート機能。keyがない場合はvueが独自に判断してくれるため、なくても画面表示はできる。

Vue公式 v-for

keyなしの例
<template>
  <div>
    <ul>
      <li v-for="arr in arrs">
        {{arr}}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data(){
    return{
      arrs:["aaa","bbb","ccc","ddd","eee"]
    }
  }
}
</script>

image.png

keyなし(エラー表示あり)でも画面表示はできる。


対処法

エラーを表示させない方法は、指定通りキーを設定すること。

:key="任意の値"
 ┗ 値は任意だがルールがある。
 ┗ 設定の仕方は2通り。
 ┗ 「:」は「v-on:」の省略形。

キーは配列の要素毎に固有の値を設定する必要がある。設定の仕方は2通り

  1. インデックス番号を用いない場合
  2. インデックス番号を用いる場合


1. インデックス番号を用いない場合

<template>
  <div>
    <ul>
      <!--keyを設定-->
      <li v-for="arr in arrs" :key="arr.id">
        {{arr}}{{arr.id}}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data(){
    return{
      arrs:["aaa","bbb","ccc","ddd","eee"]
    }
  }
}
</script>

設定するキーの値は要素毎に固有。
上記例では、arr.idとしたが、arr.の後ろはなんでもいい。arr.xxxとかでも機能する。

※注意点
設定したkeyは、vue専用のためインデックス番号として呼び出せない

image.png

{{arr.id}}は描画されない。


2. インデックス番号を用いる場合

インデックス番号を使う場合は、その変数をキーに活用することができる。

v-forでインデックス番号を参照したい場合は、第2引数を指定する。

v-for="要素用の変数, インデックス番号用の変数 in 配列"

インデックス番号用の変数は各要素毎に固有なため、この値を使うことができる。

<template>
  <div>
    <ul>
      <!--keyを設定-->
      <li v-for="(arr, index) in arrs" :key="index">
        {{arr}}{{index}}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data(){
    return{
      arrs:["aaa","bbb","ccc","ddd","eee"]
    }
  }
}
</script>

image.png

{{index}}も問題なく表示されている。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue/require-v-for-keyエラーの発生原因と対処法。templateタグでv-forを使う際の注意点。

vue/require-v-for-keyエラーの発生原因と対処法

VScodeでv-for使用中に下記エラーが発生したので、その原因と対処法について。

また、templateタグでv-forを使う際は書き方が異なるため、その注意点について。

[vue/require-v-for-key]
Elements in iteration expect to have 'v-bind:key' directives.eslint-plugin-vue

image.png

発生原因

・エラー通知はeslint-plugin-vueの機能による。
・v-for使用時はkey属性の使用が推奨されるため

あくまで推奨のサポート機能。keyがない場合はvueが独自に判断してくれるため、なくても画面表示はできる。

Vue公式 v-for

keyなしの例
<template>
  <div>
    <ul>
      <li v-for="arr in arrs">
        {{arr}}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data(){
    return{
      arrs:["aaa","bbb","ccc","ddd","eee"]
    }
  }
}
</script>

image.png

keyなし(エラー表示あり)でも画面表示はできる。


対処法

エラーを表示させない方法は、指定通りキーを設定すること。

:key="任意の値"
 ┗ 値は任意だがルールがある。
 ┗ 設定の仕方は2通り。
 ┗ 「:」は「v-on:」の省略形。

キーは配列の要素毎に固有の値を設定する必要がある。設定の仕方は2通り

  1. インデックス番号を用いない場合
  2. インデックス番号を用いる場合


1. インデックス番号を用いない場合

<template>
  <div>
    <ul>
      <!--keyを設定-->
      <li v-for="arr in arrs" :key="arr.id">
        {{arr}}{{arr.id}}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data(){
    return{
      arrs:["aaa","bbb","ccc","ddd","eee"]
    }
  }
}
</script>

設定するキーの値は要素毎に固有。
上記例では、arr.idとしたが、arr.の後ろはなんでもいい。arr.xxxとかでも機能する。

※注意点
設定したkeyは、vue専用のためインデックス番号として呼び出せない

image.png

{{arr.id}}は描画されない。


2. インデックス番号を用いる場合

インデックス番号を使う場合は、その変数をキーに活用することができる。

v-forでインデックス番号を参照したい場合は、第2引数を指定する。

v-for="要素用の変数, インデックス番号用の変数 in 配列"

インデックス番号用の変数は各要素毎に固有なため、この値を使うことができる。

<template>
  <div>
    <ul>
      <!--keyを設定-->
      <li v-for="(arr, index) in arrs" :key="index">
        {{arr}}{{index}}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data(){
    return{
      arrs:["aaa","bbb","ccc","ddd","eee"]
    }
  }
}
</script>

image.png

{{index}}も問題なく表示されている。


templateタグ内でv-forを使う際の注意点

v-forで作成する要素をブロックとして扱いたい場合、templateタグを使うが、その際のエラー挙動は別物になる。

templateタグを使った場合、keyはtemplateの下のタグに設置する必要がある。

v-forを設置したtemplateタグではない。

▼エラー例:キーがない場合
image.png
keyがない場合、v-forのあるtemplateタグではなく、直下のタグでエラーが発生している。



▼エラー例:templateタグにkeyを設定した場合
image.png

[vue/no-v-for-template-key]
'<template v-for>' cannot be keyed. Place the key on real elements instead.eslint-plugin-vue

templateタグにはkeyを設定できないと教えてくれる。


エラーの対処法

templateタグの直下のタグに要素毎に固有のkeyを設定すればOK。

▼任意にkeyを設定した場合
image.png

▼インデックス番号を使った場合
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【node+Vue】ストア+APIを経由してサーバで作ったバイナリファイル受け取りダウンロードさせる

リアクティブでSPAでなければ、サーバサイドにPOSTしてファイルをダウンロードさせれば簡単だけど
v-router とか使うと簡単には行かず、困ったのだけれど、
以下の方法で受け渡しできたので備忘録として記載しておく。

【仕様】
・ダウンロードボタンを押すと、サーバ側で作成されたファイルをダウンロードする。

【前提】
・vue から直接サーバ側へはPOSTできない。(ルーターが邪魔する。)
・ストア+API経由でサーバ側とのデータのやり取りはできる。

【サーバ側実装】
今回は、API経由でデータが渡されると、サーバ側でファイルを作成し、base64エンコードした内容を
json形式で { DATA: xxxx } として返すようにした。

【クライアント側実装】
こんな感じ
今回の肝は以下の2箇所。
・base64 をデコードする部分
・blobファイルをダウンロードさせる部分。

methods: {
  /**
   * ダウンロード
   */
  async download() {
    let info = { // api に渡す引数があれば、こんな感じで受け渡す。
      name: "download.csv"
    };
    let data = await this.$store.dispatch("download", { info });

    // base64をデコードする。
    let base64 = data["DATA"];
    let bin = atob(base64.replace(/^.*,/, ''));
    let buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
      buffer[i] = bin.charCodeAt(i);
    }

    // 以下のコードが実行されると、勝手にファイルがダウンロードされる。
    const blob = new Blob([buffer.buffer], { type: 'application/csv'});
    let link = document.createElement('a');
    link.href = window.URL.createObjectURL(blob);
    link.download = 'download.csv';
    link.click();
  }
},

download() を呼び出すボタンはこんな感じ。

<v-btn text @click="download" v-bind="attrs" v-on="on">
<v-icon>mdi-download</v-icon>
</v-btn>

直接、サーバサイドにPOSTできれば簡単だったけれど、そうすると他とセオリーが異なってしまう。
ボタンが押されると、すぐ遷移してそちらで処理されるのとは異なり、
ボタンが押されると非同期で axios 通信が行われ、データを受け取った後にダウンロードされることになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

日付時刻をpickerで入力できるvue-datetime

こんな感じで、日付と時刻のpicker入力が実装できます。

demo

Vuetify.jsにdate pickerとtime pickerはありましたが、2つを同時にできるものはなかったので探してみたところ、vue-datetime というものがありましたので、有難く使わせていただきます。

使用方法

vue-datetimeをインポート

import { Datetime } from 'vue-datetime'
import 'vue-datetime/dist/vue-datetime.css'

componentsに追加

components: {
     datetime: Datetime
},

datetimeタグにtypeで「datetime」を指定すると使用できる。
dateやtimeを指定すると片方だけの使用も可能です。

<datetime v-model="○○" type="datetime"></datetime>

ラベルのつけ方

<datetime v-model="○○" type="datetime" input-id="startDate">
   <label slot="before" for="startDate">ラベル名</label>
</datetime>

その他パラメータ

Parameter Type Default Description
v-model (required) ISO 8601 String - Datetime.
type String date Picker type: date, datetime or time.
input-id String '' Id for the input.
input-class String, Array or Object '' Class for the input.
input-style String, Array or Object '' Style for the input.
hidden-name String null Name for hidden input with raw value. See #51.
value-zone String UTC Time zone for the value.
zone String local Time zone for the picker.
format Object or String DateTime.DATE_MED, DateTime.DATETIME_MED or DateTime.TIME_24_SIMPLE Input date format. Luxon presets or tokens.
phrases Object {ok: 'Ok', cancel: 'Cancel'} Phrases.
use12-hour Boolean false Display 12 hour (AM/PM) mode
hour-step Number 1 Hour step.
minute-step Number 1 Minute step.
min-datetime ISO 8601 String null Minimum datetime.
max-datetime ISO 8601 String null Maximum datetime.
auto Boolean false Auto continue/close on select.
week-start Number auto from locale if weekstart is available or 1 First day of the week. 1 is Monday and 7 is Sunday.
flow Array Depends of type Customize steps flow, steps available: time, date, month, year. Example: ['year', 'date', 'time']
title String '' Popup title.
hide-backdrop Boolean false Show/Hide backdrop.
backdrop-click Boolean true Enable/Disable backdrop click to cancel (outside click).
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む