VueQuest第4回の講義では、下記の内容について学んでいきましょう。

  • ドラッグ&ドロップ機能
  • サーバと通信して動画削除機能を実装
  • つくったアプリを世界に公開

今回は、ドラッグ&ドロップを利用した動画の削除機能の実装に加え、作成頂いた皆さんのアプリを全世界に公開していきたいと思います。今回も楽しんでいきましょう!

動画コメントドラッグ処理

完成形のアプリのように、動画下のコメントをドラッグできるようにしていきたいと思います。

Movies.vueの変更

下記を参考に、コードを書き換えて下さい。

          <v-col
            v-for="item in movieInternalItems" :key="item.id"
            cols="12"
            sm="6"
            md="4"
            class="pr-0"
            style="width:290px"
          >
              <div
                style="text-align:center"
              >
                  <iframe
                    :src="item.url"
                    width="290"
                    height="163.125"
                    frameborder="0"
                  />
                  <v-card
                    draggable
                    @dragstart="dragMovie(item.id)"
                    color="blue"
                    height="50"
                    width="290"
                    style="margin:auto"
                  >
                    <v-col>
                      <p
                        style="color:white"
                      >
                        {{ item.comment }}
                      </p>
                    </v-col>
                  </v-card>
              </div>
          </v-col>
      // IDの降順に並び換え
      descending (a, b) {
        let comparison = 0
        if (a.id > b.id) {
          comparison = -1
        } else if (b.id > a.id) {
          comparison = 1
        }
        return comparison
      },
      // 動画ドラッグ処理
      dragMovie (id) {
     console.log(id)
      },
    },
  }

まず、動画のコメント部分をカードのような見た目とするためにv-cardタグで覆って、スタイルも整えてやります。

ポイントはv-cardタグの中に書いた属性:draggable です。要素がこの属性を持つと、ドラッグが可能となります。@dragstartというのはJavaScriptのイベントで、ドラッグ可能な要素の上でドラッグを開始した瞬間に実行されるイベントです。

今回はドラッグ開始の瞬間にdragMovieというメソッドが実行されるようにしたいと思います。dragMovieのカッコの中に書かれている引数は、v-forで繰り返し処理をしている動画情報のうちのIDを意味しています。引数としてIDを持ってくることによって、「どの動画のコメントをドラッグしているのか」を識別できるようにしています。

ここまでできたら、実際にドラッグ処理を試してみましょう。ドラッグした瞬間にディベロッパーツールのコンソールに動画のIDが表示されればOKです。表示されたIDがドラッグした動画のIDと一致するかどうかも確認しておいて下さい。

動画ドラッグ&ドロップ処理

次に、ドロップさせる処理を書いていきたいと思います。

Movies.vueの変更

下記を参考に、コードを書き換えて下さい。

<template>
  <div>
    <v-row>
      <template
          v-if="loading"
      >
          <v-container
              class="px-10 py-10"
              style="text-align:center"
          >
              <v-progress-circular
                  size="70"
                  color="blue"
                  indeterminate
              />
          </v-container>
      </template>
      <template
        v-else
      >
          <v-col
            v-for="item in movieInternalItems" :key="item.id"
            cols="12"
            sm="6"
            md="4"
            class="pr-0"
            style="width:290px"
          >
              <div
                style="text-align:center"
              >
                  <iframe
                    :src="item.url"
                    width="290"
                    height="163.125"
                    frameborder="0"
                  />
                  <v-card
                    draggable
                    @dragstart="dragMovie(item.id)"
                    color="blue"
                    height="50"
                    width="290"
                    style="margin:auto"
                  >
                    <v-col>
                      <p
                        style="color:white"
                      >
                        {{ item.comment }}
                      </p>
                    </v-col>
                  </v-card>
              </div>
          </v-col>
      </template>
    </v-row>
    <trash
      :dropped-movie-id="droppedMovieId"
    />
  </div>
</template>
<script>
  import Trash from './Trash.vue'

  export default {
    props: {
      movieItems: {
        type: Array,
        default: null,
      },
      loading: {
        type: Boolean,
        required: true,
        default: true,
      },
    },

    components: {
      Trash,
    },

    data () {
      return {
        movieInternalItems: [],
        droppedMovieId: 0,
      }
    },
      // IDの降順に並び換え
      descending (a, b) {
        let comparison = 0
        if (a.id > b.id) {
          comparison = -1
        } else if (b.id > a.id) {
          comparison = 1
        }
        return comparison
      },
      // 動画ドラッグ処理
      dragMovie (id) {
        this.droppedMovieId = id
      },
    },
  }

trashというゴミ箱を表す新たな子コンポーネントを作って、そのコンポーネントにドラッグされた動画IDを渡すことで削除機能を実装していきたいと思います。値を渡す方法は例によってpropsを使います。

Trash.vueの作成

下記を参考に、新しくファイルを作成下さい。

<template>
  <v-row
    class="my-9"
  >
    <v-spacer />
    <v-col
      cols="12"
      sm="6"
      md="4"
      style="text-align:center"
      @dragover.prevent
      @drop="dropOnTrash"
    >
      <v-card
        class="text-center:my-10"
        style="border:outset 5px red"
      >
        <span
          style="color:#CC0033; font-size:20px"
        >
          Drag & Drop Movie's Comment Here!
        </span>
        <v-icon
          color="red"
          style="font-size:50px"
        >
          mdi-trash-can
        </v-icon>
      </v-card>
    </v-col>
  </v-row>
</template> 

<script>
  export default {
    props: {
      droppedMovieId: {
        type: Number,
        default: 0,
      },
    },

    methods: {
      // 動画削除処理
      dropOnTrash () {
        console.log(this.droppedMovieId)
      },
    },
  }
</script>

ゴミ箱のサイズの幅は、ウインドウサイズによってレスポンシブに対応させたいので、vuetifyのグリッドシステムを使って上手くレスポンシブになるようにしています。

vuetifyの機能のひとつに、アイコンを簡単に取得できるというものがあり、今回はv-iconという形でアイコンを取得しています。今回であれば、v-iconタグの中にmdi-trash-canと指定するだけでゴミ箱のアイコンを表示させることができています。アイコンについて詳しく知りたい方は、vuetifyの公式ページをご覧下さい。

次に、dropOnTrashという動画の削除を実行するメソッドを定義していきます。取っ掛かりとして、まずは親コンポーネントから取得してきた削除対象の動画IDをコンソールに表示させることをやっていきましょう。

@dropというのは、ドラッグされた対象物が要素の上でドロップされた瞬間に走るJavaScriptイベントのことです。

@dropの前に、@dragoverというイベントが指定されています。これはドロップの前に走るイベントで、ドラッグされた対象物が要素の上に差し掛かった瞬間に実行されるイベントです。@dragoverの後に.preventと書いているのが、「イベント修飾子」といわれるものです。

なぜイベント修飾子が使われるのでしょう。今回でいえば、dropイベントを記述するだけだと、dragoverイベントが邪魔をして、dropイベントが実行されませんので、dragoverイベントの作用を打ち消す必要があります。つまり、dropイベントを実行させるために、dragoverの通常の挙動を止めてやらないといけません。挙動を止める処理がイベント修飾子.preventによって実行されているということです。

ちなみに、Vue.jsなどのフレームワークを用いない生のJavaScriptでは、preventDefault()という関数を使って下記のように書かれます。下記のVueバージョンが.preventです。

myButton.addEventListener( 'click', function(event){
  event.preventDefault(); // このclickイベントの通常の挙動を止める
});

ここまでできたら、動画コメントをドラッグ&ドロップしてみましょう。ドラッグ対象の動画IDとゴミ箱の上でドロップした際にコンソールに表示されるIDが一致すればドラッグ&ドロップ機能は完成したことになります。

動画削除機能の完成

ドラッグ&ドロップ機能ができましたので、ドロップされた瞬間に削除される処理をつくっていきましょう。$emitで子コンポーネントから親コンポーネントに削除処理を伝達していきたいと思います。今回作っていく削除処理の伝達の流れは下記の通りです。

$emitを使った削除処理の伝達フロー

Trash.vue → Movies.vue → Top.vue

孫コンポーネントであるTrash.vueから、親コンポーネントのMovies.vueに値を渡して、最後に祖父母コンポーネントともいえるTop.vueに値を引き継いでいきます。最終的にTop.vueまで値を渡してから削除処理を行うのは、今までの$emitの復習という意味もありますが、動画取得・投稿・削除処理などのサーバとの通信処理をTop.vueにまとめて置いておく(役割を明確化する)ためです。

$emitは今までの復習ですが、少しややこしいので、一歩一歩着実に理解していきましょう。

Trash.vueの変更

下記を参考に、コードを書き換えて下さい。

    methods: {
      // 動画削除処理
      dropOnTrash () {
        this.$emit('deleteMovie', this.droppedMovieId)
      },
    },

復習となりますが、$emitを使って、deleteMovieという親(Movies.vue)のイベントを強制的に発火させるのと同時に、droppedMovieIdを親に渡しています。

Movies.vueの変更

下記を参考に、コードを書き換えて下さい。

    <trash
      :dropped-movie-id="droppedMovieId"
      @deleteMovie="deleteMovie"
    />
      // 動画ドラッグ処理
      dragMovie (id) {
        this.droppedMovieId = id
      },
      // 動画削除処理
      deleteMovie (id) {
        this.$emit('deleteMovie', id)
      },

子のTrash.vueから発火したいイベントを@deleteMovieで指定しています。

またdeleteMovieメソッドでは、$emitを使って、deleteMovieという親(Top.vue)のイベントを強制的に発火させるのと同時に、子(Trash.vue)から受け取ったidを親に渡しています。

なお、@deleteMovie="deleteMovie"という形で、テンプレート部分には引数を指定していないのに、scriptにメソッドとして記述しているdeleteMovieには、deleteMovie (id)という形で引数を指定しています。

少し複雑なのですが、テンプレートで引数を指定しなかった場合子コンポーネントで指定した引数(この場合、Trash.vueで記述した this.$emit('deleteMovie', this.droppedMovieId) の this.droppedMovieId という引数)をそのままメソッドの引数として持ってくることができると覚えて下さい。

Top.vueの変更

下記を参考に、コードを書き換えて下さい。

    <movies
      ref="movies"
      :movie-items="movieItems"
      :loading="loading"
      @deleteMovie="deleteMovie"
    />
      getMovies () {
        axios.get('https://youtube-curation.herokuapp.com/rest/1'
        ).then((response) => {
          console.log(response.data.user.movies)
          this.movieItems = response.data.user.movies
          setTimeout(() => {
            this.loading = false
          }, 1000)
        }).catch((error) => {
          console.log(error)
          this.responseError = ['動画の取得に失敗しました']
        }).finally(() => {
          this.$refs.movies.init() 
        })
      },
      storeMovie (movieUrl, comment) {
        this.loading = true
        this.responseError = []
        axios.post('https://youtube-curation.herokuapp.com/rest', {
          url: movieUrl,
          comment: comment,
        }).then((response) => {
          this.movieItems = response.data.movies
          setTimeout(() => {
            this.loading = false
          }, 1000)
        }).catch((error) => {
          console.log(error)
          this.responseError = ['動画の投稿に失敗しました']
        }).finally(() => {
          this.$refs.movies.init() 
        })
      },
      deleteMovie (id) {
        this.loading = true
        this.responseError = []
        axios.delete('https://youtube-curation.herokuapp.com/rest/' + id
        ).then((response) => {
          console.log(response.data.movies)
          this.movieItems = response.data.movies
          setTimeout(() => {
            this.loading = false
          }, 1000)
        }).catch((error) => {
          console.log(error)
          this.responseError = ['動画の削除に失敗しました']
        }).finally(() => {
          this.$refs.movies.init() 
        })
      },

子のMovies.vueから発火したいイベントを@deleteMovieで指定しています。

deleteMovieメソッドでは、サーバと通信して行う削除処理を記述しています。

削除処理では、axios.deleteという関数を使います。また、サーバと通信するURLが削除するIDによって変わってくるため、URLの最後に + id と書くことで動的にURLが変化するようにしています。それ以外の部分は、動画取得処理・動画投稿処理と大きくは変わりません。

前回までは、setTimeout関数はfinallyのところで記述していましたが、ここでthenの部分へと移動させています。これは、見た目の問題上、サーバでの処理に成功した場合のみ、ロード中表示を終える(loadingをfalseにする)ようにしたいためです。逆にいうと、サーバ処理に失敗したら、ロード中のままにして動画情報を表示させないようにしています。動画削除処理と合わせて、動画取得処理・動画投稿処理も、setTimeout関数の位置を変えましょう。

これで、もう一度コメントのドラッグ&ドロップを実行してみましょう。上手く動画が消えれば削除処理が成功しています。

アプリを世界に公開しよう!

ここまで皆さんに作成いただいたアプリケーションを、世界に公開していきたいと思います。

デプロイのための distディレクトリ作成

vue-questディレクトリにご自身がいることを確認してから、下記コマンドにてデプロイ用のdistディレクトリを作成して下さい。

$ npm run build

上記コマンドでbuildを実行すると、プロジェクトのディレクトリ構成に変化が現れ、vue-questディレクトリ直下に、distディレクトリが現れます。

#1の講義でもお伝えしましたが、サーバにアプリをデプロイする際に、コードを圧縮する「ミニファイ化」が行われます。今まで開発してきたsrcディレクトリをミニファイ化して圧縮したものが、distディレクトリだと考えて頂ければOKです。

新しく作成されたdistディレクトリを使ってデプロイ作業を引き続きやっていきます。

Netlifyを使ったデプロイ

今回は、簡単にデプロイが行える静的ホスティングサービス「Netlify」を利用してアプリを世界に公開していきましょう。

まず、Netlifyに新規登録をして下さい。下記ページのメニューの「Sign up」より新規登録画面に移動できます。

https://www.netlify.com/

新規登録とログインが出来ましたら、下記からデプロイを行います。

上記画像の通り、メインメニューの「Sites」を押すと、赤い四角で囲った領域が現れます。

Windowsのエクスプローラや、Macのファインダーでvue-questのディレクトリ内を表示して頂き、赤い四角で囲った領域に、vue-questプロジェクト内にあるdistディレクトリだけをドラッグ&ドロップして下さい。

あまりにも簡単で驚かれるかも知れませんが、これだけであなたのプロジェクトを世界に公開することができます。

ドラッグ&ドロップすると出てくるURLをクリックすると、すぐに公開されたアプリを閲覧できます。

アプリのURLを変更したい場合も、上記画像のSite settingから下記のリンクを辿っていきますと、URLを変更可能です。

Site setting > General > Site details > Change site name

Change site nameからURL(サイト名)を指定して、Saveボタンを押すと、URLが変更されます。

私の方でアップした今回のアプリのURLを下記に貼っておきます。ご自身で作られたアプリとの比較等にご活用下さい。

デプロイ 完了後のアプリ例

https://youtube-curation.netlify.app/

この記事が気に入ったら
フォローしよう

最新情報をお届けします

Twitterでフォローしよう

おすすめの記事