20190503のvue.jsに関する記事は5件です。

モダンな技術を全く知らないSIer5年目題Webサービスを作ることになったため0から勉強する〜Nuxt +メール認証+パスワードリセット〜

はじめに

現在SIer5年目でjavascript(Jqueryのみ)、PHP(フレームワーク無し)を2年ほど、C#(Windowsアプリ)3年ほどやってきました。
色々なご縁があり、個人で最近Webサービスの立ち上げをやることになったのですが何せ本当にWebサービスを立ち上げるための知識がほぼ0に等しいです:sob:

ただ今後のキャリアを考えた時に今のままではいけないと思いチャレンジすることにしました。

まずは最初に技術を習得しないといけないので、学ぶ&アウトプットするために毎回投稿していこうと思います。
今後身についていこうと思ってるのは下記のような技術です。
AWS
Docker
CI/CD環境の構築
Laravel
Nuxt.js
今回はLaravel+Nuxtについて学んでいきます。

今回学ぶこと

Laravel+Nuxtでのメール認証機能とパスワードリセット機能を作成していこうかと思います。
今回はLaravel API側の実装をしていきます

参考サイト

jwt-auth公式サイト

こちらのサイトを参考にさせていただきました

メール認証の概要は個々がわかりやっすかったです。

前提

Laravel 5.8
Nuxt 2.5.4
jwt-auth

メール認証とパスワードリセットのAPI側実装はこちらで実装してるのでこちらを参照ください

最終的なソースコードはこちらになります。
※今回は、Nuxt側の実装でLaravel側も変更している箇所もあるのでそちらもソースコード確認してもらえたらと思います。

Nuxt側のvueファイルを作成する

フロント側はメール認証+パスワード再発行を実現するための画面とミドルウェアを作成します。

vue_file 概要
client/pages/auth/resetpassword.vue パスワードを変更する画面、パスワード変更時にトークンも一緒に送る
client/pages/auth/resendverify.vue メール認証のリンクを送るための画面
client/pages/auth/forgotpassword.vue メールアドレスを入力し、パスワード再発行メールを送信する画面
client/pages/auth/emailverification.vue メール認証中の際に表示する画面
client/middleware/emailVerify.js メール認証済みかチェックを行うミドルウェア

パスワードリセットを実装する

まずはパスワードリセットを実装するにあたっての処理の流れはこんな感じになります。

  1. ログイン画面からForgot Your Passwordリンクをクリックし、パスワード再発行のメールアドレスを入力するforgotpassword.vueを表示する
  2. メールアドレスを入力し、再発行ボタンを押下するとlocalhost/api/auth/password/emailAPIに対してリクエストが投げられ指定したアドレスにパスワード再発行メールを送る
  3. メールのリンクをクリックするとresetpassword.vueを表示し、リセットするパスワードを入力し、リンクに乗ってるqueryURLに対してリクエストを投げる
  4. パスワードリセットが完了したら、ログイン画面に遷移する

ログイン画面からforgotpassword.vueに遷移するリンクを作成

client/pages/auth/login.vue
<div class="form-group">
  <input type="submit" value="Login" class="btn btn-default w-100">
  <nuxt-link class="nav-link" to="/auth/forgotpassword">Forgot Your Password</nuxt-link>
</div>

パスワードリセット対象のメールアドレス入力画面を実装する

client/pages/auth/forgotpassword.vue
<template>
  <div class="container">
    <div class="col-md-6 offset-md-3">
      <div class="card mt-4">
        <div class="card-header">
          <p class="mb-0">ForgotPassword</p>
        </div>
        <div class="card-body">
          <b-alert variant="success" v-model="showSuccessAlert">I have sent a password reissue email</b-alert> <!-- パスワードリセットメールを送れたことをメッセージで表示する -->
          <form @submit.prevent="ReSendVerifyEmail">
            <div class="form-group">
              <label>Email</label>
              <input v-model="form.email" type="email" class="form-control" :class="{ 'is-invalid': errors.email }" placeholder="Email">
            </div>
            <div class="form-group">
              <input type="submit" value="SendResetLinkEmail" class="btn btn-default w-100">
            </div>
          </form>
        </div>
      </div>
    </div>

  </div>
</template>

<script>
  export default {
    middleware: 'guest', //ログインであればリダイレクトする
    data() {
      return {
        form: {
          email: '',
        },
        showSuccessAlert: false

      }
    },
    methods: {
      async ReSendVerifyEmail(){
        // パスワードリセットのメール送信APIを実行する
        await this.$axios.post('/auth/password/email', this.form)
          .then(data => {
            // 送信完了メッセージ表示
            this.showSuccessAlert = true;

          })
          .catch(err=> {

            console.log(err);
          });


      }
    }

  }
</script>

リセットするパスワード入力する画面

client/pages/auth/resetpassword.vue
<template>
  <div class="container">
    <div class="col-md-6 offset-md-3">
      <div class="card mt-4">
        <div class="card-header">
          <p class="mb-0">Register</p>
        </div>
        <div class="card-body">
          <form @submit.prevent="ResetPassword"> <!-- 標準のsubmitは実行しない -->
            <div class="form-group">
              <label>Email</label>
              <input v-model="form.email" type="email" class="form-control" :class="{ 'is-invalid': errors.email }" placeholder="Email">
              <div class="invalid-feedback" v-if="errors.email">
                {{ errors.email[0] }}
              </div>
            </div>
            <div class="form-group">
              <label>Password</label>
              <input v-model="form.password" type="password" class="form-control" :class="{ 'is-invalid': errors.password }" placeholder="Password">
              <div class="invalid-feedback" v-if="errors.password">
                {{ errors.password[0] }}
              </div>
            </div>
            <div class="form-group">
              <label>Password Confirmation</label>
              <input v-model="form.password_confirmation" type="password" class="form-control" :class="{ 'is-invalid': errors.password }" placeholder="Password">
              <div class="invalid-feedback" v-if="errors.password">
                {{ errors.password[0] }}
              </div>
            </div>
            <div class="form-group">
              <input type="submit" value="Register" class="btn btn-default w-100">
            </div>
          </form>
        </div>
      </div>
    </div>

  </div>
</template>

<script>
  export default {
    middleware: 'guest', //ログイン状態であればリダイレクトする
    data() {
      return {
        form: {
          email: '',                  // リセット対象のメールアドレス
          password: '',               // 新しいパスワード
          password_confirmation: '',  // 新しいパスワード確認
          token: ''                   // パスワードリセット実行するための一時的なトークン
        },
        requestUrl: ''
      }
    },

    created() {
      this.setQuery()
    },

    methods: {
      async ResetPassword(){ // パスワードリセットリクエストを投げる関数
        await this.$axios.$post(this.requestUrl, this.form)
          .then(data => {
            this.$router.push('/auth/login');
          })
          .catch(err=> {
            console.log(err);
          });
      },
      setQuery() { // getリクエストのパラメータを取得する関数
        this.requestUrl = this.$route.query.queryURL || ''; // パスワードリセットAPIのURL
        this.form.token = this.$route.query.token || '';    // パスワードリセットするために必要なToken
      },
    }

  }
</script>

メール認証を実装する

メール認証を実装するにあたっての処理流れはこんな感じになります

  1. ユーザ登録後にメール認証するためのメールを送信する
  2. メールからメール認証画面に遷移する
  3. メール認証画面では、queryURLにそのままリクエストを投げてメール認証を行い、成功したら画面遷移を行う
client/pages/auth/emailverification.vue
<template>
  <div class="container">
    <div class="col-md-6 offset-md-3">
      <div class="card mt-4">
        <div class="card-header">
          <p class="mb-0">During Verification Your Email Address</p>
        </div>
        <div class="card-body">
          <label>Validating your email address.</label>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    middleware: 'auth', //未ログイン状態であればリダイレクトする
    data() {
      return {
        queryURL: ''
      }
    },

    async mounted() {
      const queryURL = this.$route.query.queryURL || '';
      if (queryURL != '') {
        await this.$axios.$get(queryURL)
          .then(data => {
            this.$auth.fetchUser();             // メール認証が完了したため、ユーザ情報を再取得する
            this.$router.push({name: 'index'});
          })
          .catch(err => {
            alert('メール認証が失敗しました。再度メール認証を行ってください。');
            this.$router.push('/auth/resendverify');
          });
      }
    },
  }
</script>

メール認証チェックを行い、メール認証が必要な画面にアクセスした場合、認証メールを再送する画面にリダイレクトする

メール認証チェックを行うミドルウェアを実装する

email_verified_atはLaravelのUserモデルにはデフォルト項目であり
メール認証された場合はここに日時が入ってるため
nullの場合は未認証と判断し、リダイレクトを行う

client/middleware/emailVerify.js
//メールアドレスが認証されていなければ、メール認証の送信画面に遷移する
export default function({ store, redirect, app }) {
  if(app.$auth.user['email_verified_at'] == null) {
    return redirect('/auth/resendverify');
  }
}

メール認証チェックを行う

ダッシュボードは未認証ユーザはアクセスできないようにする

client/pages/dashboard.vue
<template>
  <div class="container">
    <h1>Welcome to the dashboard</h1>
  </div>
</template>

<script>
  export default {
    middleware: 'auth',
    middleware: 'emailVerify' // メール認証チェックを行う
  }
</script>

認証メールの再送信画面を実装する

client/pages/auth/resendverify.vue
<template>
  <div class="container">
    <div class="col-md-6 offset-md-3">
      <div class="card mt-4">
        <div class="card-header">
          <p class="mb-0">Verify Your Email Address</p>
        </div>
        <div class="card-body">
          <b-alert variant="success" v-model="showSuccessAlert">I have sent a password reissue email</b-alert>
          <label>Email verification has not been completed yet.
            Please press the button below to complete e-mail authentication</label>
          <div>
            <b-button block variant="primary" @click="ReSendVerifyEmail">Verify Email</b-button>
          </div>
        </div>
      </div>
    </div>

  </div>
</template>

<script>
  export default {
    middleware: 'auth', //ログインしてなければリダイレクトする
    data() {
      return {
        form: {
          email: '',
        },
        showSuccessAlert: false,
      }
    },

    methods: {
      async ReSendVerifyEmail(){
        await this.$axios.post('/auth/email/resend', this.form)
          .then(data => {
            this.showSuccessAlert = true;
          })
          .catch(err=> {
            console.log(err);
          });
      },

    }

  }
</script>

まとめ

フロント側はLaravelのmake:authで作成されるViewを参考に作りました
独学なので、おそらくもっと良い実装方法はあるかと思います。。。
もしこうした方がいいとかこのサイトは参考になるよなどあれば教えてもらえると幸いです。

あと、ログイン情報を保持するなどの機能はまだ実装してないのでそちらも実装できたらと思います。

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

VueでKonva.jsとcanvasを使ってお絵描き(その5)

その4はこちら

直線を引けるようにする

モードで直線を選択したら直線を引けるようにしようと思います。

・描画開始(マウスダウン)時にその時の座標を取得
・描画中(マウスムーブ)は何もしない
・描画終了(マウスアップ)時にその時の座標を取得

...開始・終了の座標を結んだら直線引けるんじゃね?って思ってやってみたらできました。

親(CallCanvas.vue)のほうはすでに直線モードを子に渡せる状態になっているので変更はありません。

子(FreeDrawing.vue)を修正します。
以下のようなコメント行の間にあるソースが直線を引くために追加したソースコードです。

/** 追加 */
...
..
.
/** 追加ここまで */
FreeDrawing.vue
...
..
.

<script>
import Konva from 'konva'

export default {
  ...
  ..
  .
  methods: {
    mousedown: function () {
      this.isPaint = true

      // マウスダウン時の座標を取得しておく
      this.lastPointerPosition = this.stage.getPointerPosition()

      // 戻す配列に描画前のキャンバスの状態を保存
      this.undoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))

      /** 追加 */
      // 直線モード時はマウスダウン時に描画開始座標を指定する
      if (this.isTargetMode('line')) {
        this.context.beginPath()

        this.localPos.x = this.lastPointerPosition.x - this.drawingScope.x()
        this.localPos.y = this.lastPointerPosition.y - this.drawingScope.y()

        // 描画開始座標を指定する
        this.context.moveTo(this.localPos.x, this.localPos.y)
      }
      /** 追加ここまで */
    },
    mouseup: function () {
      this.isPaint = false

      /** 追加 */
      // 直線モード時はマウスアップ時に描画する
      if (this.isTargetMode('line')) {
        this.pos = this.stage.getPointerPosition()
        this.localPos.x = this.pos.x - this.drawingScope.x()
        this.localPos.y = this.pos.y - this.drawingScope.y()

        // 描画開始座標から、lineToに指定された座標まで描画する
        this.context.lineTo(this.localPos.x, this.localPos.y)
        this.context.closePath()
        this.context.stroke()
        this.drawingLayer.draw()

        this.lastPointerPosition = this.pos
      }
      /** 追加ここまで */
    },
    mousemove: function () {
      if (!this.isPaint) {
        return
      }
      // ペンモード時
      if (this.isTargetMode('brush') || this.isTargetMode('line')) {
        this.context.globalCompositeOperation = 'source-over'
      }
      // 消しゴムモード時
      if (this.isTargetMode('eraser')) {
        this.context.globalCompositeOperation = 'destination-out'
      }

      /** 追加 */
      // 直線モード時は何もしない
      if (this.isTargetMode('line')) {
        return
      }
      /** 追加ここまで */

      ...
      ..
      .
    },
    ...
    ..
    .
}
</script>

引けました!ただ斜め線引いた時に少しギザギザなってしまうところが難点。
(考えを安直にソースにしただけなので仕方ないかもしれないですが。)
image.png

備考

直線を引くときにガイドラインを表示してあげたりとかできるとさらに良くなりますね。
気が向いたらやってみます。

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

vue cliでプロジェクト作ったら(メモ)

前記事のURL

https://qiita.com/s12ac098/items/f24ac76167b3d846064e

プロジェクトのディレクトリで「run npm serve」

npmrundev.jpg

localhost:8080にアクセス

runserve.jpg

デフォルトのプロジェクトができます

default.jpg

デフォルトをいじる Part1~HelloWorld.vueのリネーム~

まずcomponentsのHelloWorld.vueをリネームしましょう(ダサいから)
helloworld.jpg

安心してください。ページが死ぬだけです。
compile.jpg

App.vueにHelloWorldがいっぱいいるので、全部indexに変えましょう。
changealloccurences.jpg

App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <index msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import index from './components/index.vue'

export default {
  name: 'app',
  components: {
    index
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

デフォルトをいじる Part2~ページをまっさらに~

つづいて、こいつを真っ白に染めましょう
default.jpg

divタグとstyleタグの中を消しましょう。

App.vue
<template>
  <div id="app">
   <h1>vue cliがまっさらに・・・</h1>
  </div>
</template>

<script>
import index from './components/index.vue'

export default {
  name: 'app',
  components: {
    index
  }
}
</script>

<style>

</style>

index.vueの中も同様にですね。※divタグのクラスはご自由に

index.vue
<template>
  <div class="index container">

  </div>
</template>

<script>
export default {
  name: 'index',
  props: {

  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>

</style>

white.jpg

つづく

次回はcomponentの追加における3ステップについて!

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

VueでバリデーションをVuelidateで実装してみた

VueでバリデーションをVuelidateで実装してみた

まずはコマンドラインでnpmを使ってVuelidateをインストールします。

npm install vuelidate --save

インストールが終了したらmain.jsにVuelidateをimportしてください。

main.js
import Vuelidate from 'vuelidate'

次にmain.jsに次の一文も追加に記述してください。

main.js
Vue.use(Vuelidate);

これで準備が整いました。

Vuelidateを使っていこう

使用したいコンポーネントにimportしていきます。

import { required } from 'vuelidate/lib/validators'

{}のなかに使いたいバリデータを記述しています。
※バリデータとはバリデーションを行うための機能の一種です。

これにより2つの機能が使えるようになります。

  • validations コンポーネントオプション - 検証の定義
  • $v 構造体 - 検証状態を保持するビューモデル内のオブジェクト

です。

validationsはVue インスタンスのコンポーネントオプションとして使用できバリデーションをかけるデータに対してどのようなバリデータをかけるかをオブジェクトで設定できます。

$vはビューモデル内で使用できるオブジェクトでこのオブジェクトにバリデーションの

結果や色々な状態の示すプロパティが入っています。このオブジェクトを見て判断をしていきます。

次に要素をv-modelディレクティブでデータバインディングしたものに設定していきます。

 <input type="email" id="email" v-model="email">
export default {
  data () {
    return {
      email: ''
    }
  },
}

上記にvalidationsコンポーネントオプションを設定します。

export default {
  data () {
    return {
      email: ''
    }
  },
  validations: {
      email: {
        required,
      }
  }
}

validationsオプションを記述してその中で今回はdataオプションの所でemailと設定しているので

同じ名前のemailと記述してその中にかけたいバリデータを記述します。

いまimportしているのはrequiredだけなのでrequiredを記述します。

記述できたら次は要素の方に新たに@blur="$v.email.$touch()"と記述していきます。

 <input type="email" id="email" v-model="email" @blur="$v.email.$touch()">

@blurはVue.jsのイベントでフォーカスがなくなった時に発火します。

$v.emailはvalidationsで設定したemailの状態のプロパティが入ったオブジェクトを呼び出してます。

$v.emailのemailの部分を変更してvalidationsで設定した違うkeyを呼び出すことも可能です。

例えば

export default {
  data () {
    return {
      email: ''
    }
  },
  validations: {
      email2: {
        required,
      }
  }
}
 <input type="email" id="email" v-model="email" @blur="$v.email2.$touch()">

と変更したらemail2の状態のプロパティが入ったオブジェクトを呼び出してます。

$touch()は$vモデルのメッソドで$v.emailの状態のプロパティを再帰的にtrueにします。

では、次に$v.emailには状態のプロパティが入ったオブジェクトだと言いましたが

どのような状態のプロパティが入っているかをテキスト展開を追加して確認してみましょう。

<input type="email" id="email" v-model="email" @blur="$v.email.$touch()">
<pre>{{$v.email}}</pre>

スクリーンショット 2019-04-29 12.15.24.png

スクリーンショット 2019-04-29 12.25.17.png

他の値も変化していますが、特に見てもらいたいのが赤線の2つです。

requiredはvalidationsオプションでemailに設定したrequiredのことを示しており

今は要素に何かしら文字が入っているからtrueになっているが

何も入っていなくてフォーカスを外すとfalseになる

$modelは検証しているモデルへの参照。Vueモデルを直に参照したときと同じ値が得られます。

なのでthis.$v.email.$modelとthis.emailは同じ値になります。

なので$modelを書き換えるとVueモデルも一緒に書き換えられます。

Styleをつけていく

validationsでバリデーションをつけることができましたがあくまで中だけのバリデーションなので

要素に入力している人は全く気づきません。

なのでこちらで$v.emailの値を参照しながらエラーになっているかどうかを示してあげないといけません。

.invalid {
  border: 1px solid red;
  background-color: #ffc9aa;
}

cssに上記を追加して要素にも動的にclassを追加します。

<input type="email" id="email" v-model="email" @blur="$v.email.$touch()" :class="{invalid: $v.email.$error}">

$v.email.$errorの値がtrueになればinvalidのclassが追加されます。

これで確認してみましょう。
スクリーンショット 2019-04-29 13.04.46.png

一度フォーカスをして何も入力しないでフォーカスを外すと$errorがtrueになっているため

class名が追加され要素が赤く表示がされています。

これでバリデーションエラーぽくなりましたね。

次はなんでエラーになっているかをテキストで表示してあげたら親切ですね。

テキストでエラー内容を表示する

要素の下にテキストを追加していきます。

<input type="email" id="email" v-model="email" @blur="$v.email.$touch()" :class="{invalid: $v.email.$error}">
<p style="color:red;" v-if="$v.email.$error">メールアドレスは必須項目です。</p>

v-if="$v.email.$error"で先程のclass名のときと同じで$v.email.$errorがtrueのときのみ

表示させるようにしています。

スクリーンショット 2019-04-29 14.20.16.png

このようにエラーしていることが分かりやすくなりました。

その他のバリデータをご紹介

email

有効なメールアドレスを受け入れます。

確認メールを送信せずにアドレスが本物であるかどうかを判断することは不可能であるため、

サーバー上で注意深く確認する必要があります。

maxLength、minLength

入力された文字の長さをバリデーションします。

numeric

数字のみを受け入れます。

alphaNum

英数字のみを受け入れます。

などなどがあります。
まだまだバリデータはあるのでそれは公式サイトをご確認ください。

まとめ

いかかでしたでしょうか?

これであなたも簡単にバリデーションをVueで実装することができます。

是非ともやってみてください!

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

Vue.jsのv-htmlで書き出したHTMLを非同期的にjQueryで更新する

goal.gif

Vue.jsのv-htmlで書き出したHTMLを非同期で書き換えてみましょう。
サンプルではユーザーの入力した文字をv-htmlで表示しています。

書き出したHTMLにimgが含まれていた場合、imgのdata-nameを元に非同期で画像データを引っ張ってきて表示する、という想定です。
(画像データを用意すると一手間かかるため、今回は代わりに絵文字を表示しています。)

自己紹介
アプリクリエイターの ミャウチー です。HTMLとCSS歴は8年になります。
アプリを作っている最中に今回の問題にぶつかって自己解決したので、ノウハウを共有します。

今回の前提条件
HTML, CSS, JavaScriptを使います。
初歩レベルのjQueryを使います。
Vue.jsは data: { 〜 }@click が分かっている前提です。
Vue.jsはCDNで実行しています。

もくじ
1. 初期状態の確認
2. Vueデータに変更があったときアラートを出す
3. v-htmlで書き出されたときアラートを出す
4. 書き出されたHTMLを取得して非同期処理をする
5. 待機中に [loading...] を出す

注意
今回作成するものは、非同期である必要のない処理を非同期に実行しています。
今回の目的が「Vue.jsのv-htmlで書き出したHTMLを非同期的にjQueryで更新する」であり、問題を単純化するためです。
実際の場面では今回の例を応用して、何かしらのAPIを非同期で実行することを想定しています。


先に完成形のコードをみたい方は、この行をクリックすると下に展開されます。

HTML:

<main>
    <input type='text' v-model='input'>
    <input type='button' value='↓' @click="output = input"> HTML
    <p v-html='output'></p>
</main>

CSS:

img[src='']::before {
    content: "[ loading... ]";
}

JavaScript:

const vm = new Vue({
    el: 'main',

    data: {
        input: '',
        output: '',
    },

    watch: {
        output: function(val) {
            const $dom = $(this.$el)

            this.$nextTick(function() {
                // 非同期的な何かしらの処理
                setTimeout(function() {

                    // data-nameで指定した値を取得
                    const name = $dom.find('img').data('name')

                    // 今回は簡易例として画像を絵文字に置き換えます
                    const character = (name === 'cat') ? '?' : '?'
                    $dom.find('img').replaceWith(`<span>${character}</span>`)

                    // APIで取得した画像などを表示する場合はこちら
                    //$dom.find('img').attr('src', "https://〜画像のURL〜.jpg")

                }, 2000)    // = 例として2秒後に実行
            })
        },
    },
})


STEP 1 初期状態の確認

01.gif

HTML:
・ユーザーの入力ボックス
・反映ボタン
・HTMLを書き出す領域
HTMLはこれ以上変更しません。

<main>
    <input type='text' v-model='input'>
    <input type='button' value='↓' @click="output = input"> HTML
    <p v-html='output'></p>
</main>

CSS:
空っぽの状態から始めます。

/* なし */

JavaScript:

const vm = new Vue({
    el: 'main',

    data: {
        input: '',
        output: '',
    },
})

実行の流れ:
1. 変数inputにユーザーの入力した文字が入る
(例: hello <img data-name='cat' src=''> world!
2. 「↓」ボタンを押すと 変数output = input となる
3. 変数outputがHTMLとして書き出される
(書き出されたとき<img〜>が表示されないのは、src属性が空っぽなためです。)

STEP 2 Vueデータに変更があったときアラートを出す

02.gif

new Vue({ 〜 }) 内の一番下に次のコードを足します。

watch: {
    output: function(val) {
        alert("outputの中身が変わりました")
    },
},

output の変更をwatchすることで、変更があったときに関数が実行されます。

STEP 3 v-htmlで書き出されたときアラートを出す

03.gif

output: function(val) { 〜 } を次のように書き換えます。

output: function(val) {
    this.$nextTick(function() {
        alert("HTMLが書き出されました")
    })
},

$nextTick() は、DOMが書き換えられた後に呼び出されます。
参照:Vue.js リアクティブの探求 #非同期更新キュー

体感的にはalertの出るタイミングがSTEP 2の時と変わりないですが、若干違います。
STEP 2のとき:DOMが書き換えられる前
STEP 3のとき:DOMが書き換えられた後
(alertと同じタイミングで console.log(this.$el.innerHTML) などとDOMの中身をコンソール出力してみると分かりやすいです)

STEP 4 書き出されたHTMLを取得して非同期処理をする

04.gif

STEP 4 - 1
output: function(val) { 〜 } 内の一番上に const $dom = $(this.$el) を書き足します。

変数$domには、v-htmlで書き出されたHTMLを含むjQueryオブジェクトが代入されます。
(厳密には、今回の場合 $dom[0] === $('main')[0] です。)

STEP 4 - 2
this.$nextTick(function() { 〜 }) の中に非同期的な何かしらの処理を書きます。
今回は例として、DOMが変更された2秒後にimgを絵文字に置き換えます。

this.$nextTick(function() {

    /* * * 非同期的な何かしらの処理 * * */
    setTimeout(function() {

        // data-nameで指定した値を取得
        const name = $dom.find('img').data('name')

        // 今回は簡易例として画像を絵文字に置き換えます
        const character = (name === 'cat') ? '?' : '?'
        $dom.find('img').replaceWith(`<span>${character}</span>`)

        // APIで取得した画像などを表示する場合はこちら
        //$dom.find('img').attr('src', "https://〜画像のURL〜.jpg")

    }, 2000)    // = 例として2秒後に実行
})

STEP 5 待機中に [loading...] を出す

05.gif

DOMが書き出されてから絵文字が表示されるまでの間、代わりの文字を出しておきます。

今回は imgタグのsrc属性が空っぽ のとき、画像の代わりに [ loading... ] を表示します。
例: <img src=''>

CSS:

img[src='']::before {
    content: "[ loading... ]";
}

まとめ

goal.gif

今回はVue.jsのv-htmlで書き出したHTMLを非同期で書き換えました。
応用としては、画像データを何かのAPIで非同期に取得したいときに使えるかと思います。

私の場合、Firebase Firestoreから文書データを引っ張ってきたときに、テキストデータをまず先に表示して、画像が含まれていたらFirebase Storageから非同期に取得する、としようとしました。
それで今回のことが分からずつまずき、「Vue.js リアクティブの探求 #非同期更新キュー」を解読しながら自己解決したので、共有しました。

実際にv-htmlを使う場合は「Vue.js テンプレート構文 #生の HTML」に書かれている通り、XSSに気をつけてください。

HTML:

<main>
    <input type='text' v-model='input'>
    <input type='button' value='↓' @click="output = input"> HTML
    <p v-html='output'></p>
</main>

CSS:

img[src='']::before {
    content: "[ loading... ]";
}

JavaScript:

const vm = new Vue({
    el: 'main',

    data: {
        input: '',
        output: '',
    },

    watch: {
        output: function(val) {
            const $dom = $(this.$el)

            this.$nextTick(function() {
                // 非同期的な何かしらの処理
                setTimeout(function() {

                    // data-nameで指定した値を取得
                    const name = $dom.find('img').data('name')

                    // 今回は簡易例として画像を絵文字に置き換えます
                    const character = (name === 'cat') ? '?' : '?'
                    $dom.find('img').replaceWith(`<span>${character}</span>`)

                    // APIで取得した画像などを表示する場合はこちら
                    //$dom.find('img').attr('src', "https://〜画像のURL〜.jpg")

                }, 2000)    // = 例として2秒後に実行
            })
        },
    },
})

質問や気になる点があれば、コメントやTwitterで連絡ください!

ミャウチー

高校一年の頃からアプリ作りに没頭していて、今はアプリクリエイターとしての人生を主軸に生きています。
おじいさんになっても縁側に座ってカタカタとアプリ作りをします。最近はアウトプットを大切にしようと奮闘中です。
サイバーブレイン株式会社( メインサービスはAI Academy )でデザイナーをしています。

Twitter | Miyauchi Akira
https://twitter.com/miyauchoi

portfolio.png
ポートフォリオ | ミヤウチアキラ
https://miyauchi-akira.app

note.png
アプリデザインで考えていること
「ポエティカルデザイン」というアプリデザイン手法 | note

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