Appearance
@click で商品を選択する 
本章の概要とゴール 
本章では、クリックイベントを定義して購入する商品をクリックで選択できるようにプログラムを改修していきます。商品をクリックすると選択中となり、選択中の商品は背景の色は変わるようにします。 本章を実践すると、v-on ディレクティブを使ってイベントリスナーの登録ができるようになります。 また、v-bind ディレクティブを使って選択中かそうでないかを判断して、動的に style を変化させることができるようになります。
実装の考え方 
どのように実装していけば良いか 1 つずつ分解して考えてみると、以下のようになります。
- 選択状態を表す style を用意する
- クリックすると商品を"選択状態"にする
- "選択状態"の時にだけ、1 の style を適用する
1 つ 1 つの処理は難しくありません。順番に実装してみましょう。
前回までのコードの確認 
現在のコードは以下のようになっています。
template
vue
<template>
  <header class="header">
    <img
      src="/images/logo.svg"
      alt="">
    <h1>Vue.js ハンズオン</h1>
  </header>
  <main class="main">
    <template
      v-for="item in items"
      :key="item.id">
      <div
        v-if="!item.soldOut"
        class="item">
        <div class="thumbnail">
          <img
            :src="item.image"
            alt="">
        </div>
        <div class="description">
          <h2>{{ item.name }}</h2>
          <p>{{ item.description }}</p>
          <span>¥<span class="price">{{ pricePrefix(item.price) }}</span></span>
        </div>
      </div>
    </template>
  </main>
</template>script
vue
<script setup>
import { ref } from 'vue'
const items = ref([
  {
    id: 1,
    name: 'アボカドディップバケット',
    description:
      '刻んだ野菜をアボカドと混ぜてディップに。こんがり焼いたバゲットとお召し上がりください。',
    price: 480,
    image: '/images/item1.jpg',
    soldOut: false
  },
  {
    id: 2,
    name: 'あの日夢見たホットケーキ',
    description:
      '子供のころに食べたかった、あのホットケーキを再現しました。素朴でどこか懐かしい味をどうぞ。',
    price: 1180,
    image: '/images/item2.jpg',
    soldOut: false
  },
  {
    id: 3,
    name: 'HOP WTR',
    description:
      'ロサンゼルス生まれのスパークリングウォーター。ノンカロリー、ノンアルコールの新感覚飲料です。',
    price: 320,
    image: '/images/item3.jpg',
    soldOut: true
  },
  {
    id: 4,
    name: 'チーズフレンチフライ',
    description:
      'イタリア産チーズをたっぷりかけたアツアツのフレンチフライ。みんな大好きな一品です。',
    price: 670,
    image: '/images/item4.jpg',
    soldOut: false
  }
])
/**
 * 価格を3桁ごとのカンマ付きで返す
 * @param {number} price 価格
 */
function pricePrefix(price) {
  return price.toLocaleString()
}
</script>1. 選択状態を表す style を用意する 
まず、選択中の商品に適用する style を用意しましょう。<style> タグに以下の style を追加します。
css
.selected-item {
  background-color: #e3f2fd;
}.selected-item という style を追加しました。選択中の商品は背景の色を変化させたいため、background-color プロパティを定義しています。
2. クリックすると商品を"選択状態"にする 
次は、クリックして商品を"選択状態"にする部分の実装です。
"選択状態"を表すプロパティの追加 
以前 items に、"売り切れかどうか"という情報を表す soldOut というプロパティを追加しました。今回も同様に、"選択状態か"という情報をプロパティとして追加しましょう。
selected というプロパティを追加し、値を true(選択状態)/ false(非選択状態)とすることで、選択しているかどうか判別できるようにします。
初期状態は何も選択されていない状態であるため、すべての商品を selected: false にしておきましょう。
vue
<script setup>
import { ref } from 'vue'
const items = ref([
  {
    id: 1,
    name: 'アボカドディップバケット',
    description:
      '刻んだ野菜をアボカドと混ぜてディップに。こんがり焼いたバゲットとお召し上がりください。',
    price: 480,
    image: '/images/item1.jpg',
    soldOut: false,
    selected: false
  },
  {
    id: 2,
    name: 'あの日夢見たホットケーキ',
    description:
      '子供のころに食べたかった、あのホットケーキを再現しました。素朴でどこか懐かしい味をどうぞ。',
    price: 1180,
    image: '/images/item2.jpg',
    soldOut: false,
    selected: false
  },
  {
    id: 3,
    name: 'HOP WTR',
    description:
      'ロサンゼルス生まれのスパークリングウォーター。ノンカロリー、ノンアルコールの新感覚飲料です。',
    price: 320,
    image: '/images/item3.jpg',
    soldOut: true,
    selected: false
  },
  {
    id: 4,
    name: 'チーズフレンチフライ',
    description:
      'イタリア産チーズをたっぷりかけたアツアツのフレンチフライ。みんな大好きな一品です。',
    price: 670,
    image: '/images/item4.jpg',
    soldOut: false,
    selected: false
  }
])
/**
 * 価格を3桁ごとのカンマ付きで返す
 * @param {number} price 価格
 */
function pricePrefix(price) {
  return price.toLocaleString()
}
</script>v-on の書き方 
商品をクリックした時に selected プロパティの値を true にすれば、「クリックして選択する」という実装が可能になります。
商品に対して click イベントのイベントリスナーを実装していきましょう。
ヒント
ボタンがクリックされた、フォームに入力された、スクロールしたなど、Web ページ上でのさまざまな動きのことをイベントと言います。イベントが発生した時に実行される処理をイベントリスナーといい、イベントが発生する要素に対して設定します。
Vue.js でイベントリスナーを登録するには v-on というディレクティブを使用し、以下のように記述します。
html
<button type="button" v-on:click="イベント時の処理">***</button>上記は <button> 要素をクリックした時に実行される click イベントを設定しています。v-on:click=" " の " " の中に、click イベントが発生した時に実行したい処理を記述できます。
click のほかにも、スクロールした時に実行される scroll イベントや、フォームが送信された時に実行される submit イベントなども用意されています。
v-on:click は、@click と省略して記述できます。
html
<button type="button" @click="イベント時の処理">***</button>click イベントの実装 
では実際に、商品に click イベントを登録していきましょう。以下の例でハイライトしている箇所を追加します。
vue
<template>
  <header class="header">
    <img
      src="/images/logo.svg"
      alt="">
    <h1>Vue.js ハンズオン</h1>
  </header>
  <main class="main">
    <template
      v-for="item in items"
      :key="item.id">
      <div
        v-if="!item.soldOut"
        class="item"
        :class="{ 'selected-item': item.selected }"
        @click="item.selected = !item.selected">
        <div class="thumbnail">
          <img
            :src="item.image"
            alt="">
        </div>
        <div class="description">
          <h2>{{ item.name }}</h2>
          <p>{{ item.description }}</p>
          <span>¥<span class="price">{{ pricePrefix(item.price) }}</span></span>
        </div>
      </div>
    </template>
  </main>
</template>v-for の中の要素は 1 つ 1 つの商品を表しています。その要素に対し @click を追加しました。
click イベントで商品の選択をするためには、その商品の selected プロパティを true にすることが必要です。それだけを実現するには @click="item.selected = true" とすれば良さそうです。 しかしその場合、一度クリックして true にすると、 false に戻すことができない、つまり選択した商品をキャンセルできなくなってしまいます。
そこで、click イベントでの処理を、selected の値が false の時は true に、true の時は false にするようにしておけば、選択とキャンセルが可能になります。つまり、現時点での selected の値と反対の値を代入すれば良いということです。これを実装すると、item.selected = !item.selected という処理になります(論理否定 ! で item.selected の否定の値を使用できます)。
3. "選択状態"の時にだけ、1 の style を適用する 
最後に、1 で作成した .selected-item という style を、selected の値が true の時にだけ適用すれば完成です。
v-bind の書き方 
今回のように、特定の条件の時にだけ class などの属性を適用させたい場合は、v-bind ディレクティブを使用します。v-bind は属性をバインディングするためのディレクティブであり、class や style、src などの属性を操作できます。
class の操作は、対象の要素に対して v-bind:class="classの制御処理" のように行います。また、v-bind:class は :class と省略して記述できます。
:class の実装 
では実際に :class を使って実装してみましょう。以下の例でハイライトしている箇所を追加します。
vue
<template>
  <header class="header">
    <img
      src="/images/logo.svg"
      alt="">
    <h1>Vue.js ハンズオン</h1>
  </header>
  <main class="main">
    <template
      v-for="item in items"
      :key="item.id">
      <div
        v-if="!item.soldOut"
        class="item"
        :class="{ 'selected-item': item.selected }"
        @click="item.selected = !item.selected">
        <div class="thumbnail">
          <img
            :src="item.image"
            alt="">
        </div>
        <div class="description">
          <h2>{{ item.name }}</h2>
          <p>{{ item.description }}</p>
          <span>¥<span class="price">{{ pricePrefix(item.price) }}</span></span>
        </div>
      </div>
    </template>
  </main>
</template>商品要素に対して、:class を追加しました。処理の中身を確認していきましょう。
js
:class="{ 'selected-item': item.selected }"上記のように、適用したい class を条件式と一緒に記述することで、その条件式が true の場合のみ該当の class を適用できます。
:class="{ 'selected-item': item.selected }" の中の selected-item が、1 で作成した style のクラス名です。selected-item は、item.selected が true の場合のみ適用されます。item.selected が true の場合とは、商品が選択されている状態を示しています。
これで、商品が選択状態の時に背景の色が変化するようになりました。
+1 チャレンジ
ここまでの学習が完了した人は、以下の内容にも挑戦してみましょう。
キー修飾子を使ったキーボードイベントの使い方 
マウスクリックではなくキーボードの操作で商品の選択を行うことがあるかもしれません。そのような時には v-on ディレクティブに対してキー修飾子を追加すると、キーボードイベントを使用できます。
キー修飾子の書き方 
v-on ディレクティブにキーボードイベントを追加するためには keyup イベントを設定します。keyup イベントには使用したいキーコードを .(ドット)でつなげて、実行したい処理を記述します。
html
<input v-on:keyup.enter="alertDialog" />vue
<script setup>
// ...省略
function alertDialog() {
  window.alert('keyup')
}
</script>keyup イベントにキーコード enter を .(ドット)でつなげて、alert メソッドを記述しました。input タグにフォーカスし、キーボードの Enter を押すと alert メソッドが実行されます。また、click イベントと同様に keyup イベントも v-on ディレクティブの省略が可能です。
html
<input @keyup.enter="alertDialog" />使用可能なキーコード 
enter 以外で Vue.js から提供されているキーコードの一覧は下記の通りです。
- .enter
- .tab
- .delete ( “Delete” と “Backspace” の両方がキャプチャされています。 )
- .esc
- .space
- .up
- .down
- .left
- .right
キー修飾子の実装 
現在 click イベントが設定されている処理を keyup イベントで置き換えてみます。
html
<template>
  <header class="header">
    <img src="/images/logo.svg" alt="" />
    <h1>Vue.js ハンズオン</h1>
  </header>
  <main class="main">
    <template v-for="item in items" :key="item.id">
      <div
        v-if="!item.soldOut"
        class="item"
        :class="{ 'selected-item': item.selected }"
        @keyup.enter="item.selected = !item.selected"
      >
        <div class="thumbnail">
          <img :src="item.image" alt="" />
        </div>
        <div class="description">
          <h2>{{ item.name }}</h2>
          <p>{{ item.description }}</p>
          <span>¥<span class="price">{{ pricePrefix(item.price) }}</span></span>
        </div>
      </div>
    </template>
  </main>
</template>しかし、このままですと要素を選択して Enter を押しても何も反応しません。div タグが静的 HTML 要素でありキーボードによるアクセスができないためです。そのため div タグへのキーボードのアクセスが可能となるように tabindex 属性を追加します。
html
<div
  v-if="!item.soldOut"
  class="item"
  :class="{ 'selected-item': item.selected }"
  @keyup.enter="item.selected = !item.selected"
  tabindex="0"
>tabindex 属性を追加することでキーボードによるアクセスが可能となったので、キーボードの Enter を押すとスタイルが変化するようになりました。もし Enter を押した時とクリックした時、どちらのイベントも有効にしたい場合は、keyup イベントと click イベントの両方を記述することで実現可能です。
html
<div
  v-if="!item.soldOut"
  class="item"
  :class="{ 'selected-item': item.selected }"
  @keyup.enter="item.selected = !item.selected"
  @click="item.selected = !item.selected"
  tabindex="0"
>今回使用したディレクティブ 
今回の実装では以下のディレクティブを使用しました。
- v-on:click(- @click)を使用したイベントリスナーの登録
- v-on:keyup(- @keyup)を使用したイベントリスナーの登録
- v-bind:class(- :class)を使用した属性の操作
このように、複数のディレクティブや処理を組み合わせて、さまざまな動きを実現できます。