カテゴリーアーカイブ Vue

著者:杉浦

【Vue】jQueryとVueを共存させる

 デザインはjQueryで作られる、実装はVue.jsで行う。なんやかんやあってこの方式でwebページを作る必要が出てくることがあります。一貫していた方が作りやすいはずなのですが、なんやかんやあってこうなります。
 デザインの実装の際、jQueryとVue.jsを読み込ませるとデザイン時に動いていたイベントが消える時があります。これはVueがテンプレートエンジンも兼ねていることが原因で起きがちです。例えば次の様なシナリオで起きます。

// デザイン時
HTML読み込み完了
↓
jQueryによるイベント付与実行
↓
全てのHTMLに付与されるべきイベントが付与されている

// 実装時
HTML読み込み中、Vue構築中
↓
HTML読み込み完了
↓
jQueryによるイベント付与実行
↓
Vue構築完了。Vue内で定義されたHTML構築を持ってきて描画

 特にVueでAPIの結果をHTMLの一領域に描画する仕組みを作った時はイベントの再定義の絡みで大体こうなります。こうなる時にはVueの中でVueが読み込み完了した場合に実行されるイベントでjQueryによるイベントを定義すると良いです。

export default {
  components: {
  },
  props: {
  },
  data() {
    return {
      
    };
  },
  mounted() {
    // jQueryのイベント付与処理
  },

 このように記述するとコンポーネントが読み込まれるたびにmounted関数が実行され、テンプレート内にjQueryのイベントが付与されます。
 Vue.jsとjQueryの同時実行はJavaScriptファイルが巨大になり初期読み込みが遅くなるため積極的に行うべきでないのですが、デザイナーからいただけるデザインにjQueryでアニメーションがついていることがしばしばあります。そういった際にこのやり方を知っておくと、Vue.jsの便利さから離れないままアニメーションを崩すことも再構築することもなくデザインを再現できます。

  • この記事いいね! (1)
著者:杉浦

【Laravel】【Vue】LaravelとVueを結びつけるパッケージは割と多い

 LaravelはPHPのフレームワークです。PHPのフレームワークですがプロジェクトを建てた時点で一緒に使えと言わんばかりにフロントエンド側のVue.jsのコードと設定が渡されます。

 このためか巷にはLaravelとVue.jsを同時に使うことを前提としたパッケージが少なくないです。
 例えば、npmです。`vue laravel`でググると170件出てきます。

 一番上のlaravel-vue-pagination – npmはLaravelのクエリビルダのpaginateメソッドをレスポンスにしたAPIとVueを組み合わせてページネーションを実現するコンポーネントの様です。こういうのを作ろうとすると端の方のページを見る時の作りが面倒になりますが、作る手間を省けます。
 Packagistにはvueタグ付きのライブラリが144件出てきます。laravel-vue-generatorsは名前だけでもう相当ですね。Laravel内のコードからVueのコードを生成する様です。

 GitHubともなるとほぼ両者の和集合に加えて、草の根開発まで加わりさらに増えます。

  • この記事いいね! (1)
著者:杉浦

【Vue.js】VueとCSSアニメーションを組み合わせる

 Vue.jsは便利です。テンプレートエンジン同然の動的なDOM操作が簡潔に記述できます。例えばv-ifの様な条件付レンダリングです。
条件付きレンダリング — Vue.js

 デモの様に10行に満たないテンプレートHTMLとVueインスタンス定義で切り替えが実現できます。これは本当に簡潔な記述で、他の記述が紛れる余地がありません。このためDOM操作の際の細やかアニメーションを自らの手で付けようとしてしまいますが(少なくとも自分はそうしました)、実はVue.js自身にアニメーションをサポートする機能がついています。
Enter/Leave とトランジション一覧 — Vue.js
transition – CSS: カスケーディングスタイルシート | MDN
 VueのEnter/Leave とトランジション一覧に記述されている機能は主にCSSのtransitionにDOM操作アニメーションを明け渡すための機能です。発展形としてJavaScriptによる処理を描画中に挟む、データの変化をアニメーションにする、といったものもあります。詳しくはVue公式ガイドのトランジションとアニメーション関連を参照です。
 具体的には

<transition name="fade">
    <p v-if="show">hello</p>
</transition>

 の様にv-ifの様な条件付レンダリング要素をtransitionタグで括ることが第一歩です。transitionタグはVue組み込みのコンポーネントのタグです。このように括るとtransitionタグ内部のv-if属性付きのタグにアニメーションをつけるための要素が適宜定義されます。定義は次の画像にまとまっています。
 
 Enter/Leave とトランジション一覧 — Vue.js#トランジションクラスより引用
 この画像のvの部分にtransitionタグのname属性の値が当てはまり、その文字列のクラスがv-ifの様な表示、描画操作対象タグにつきます。具体的には次の様に使えます。

 CSSのtransitionらしく起点、終点、起点から終点への移動の仕方、のアニメーションを指定します。それぞれ無印、to、activeが当てはまります。デモのslideならばmax-heightの値を0.5秒で0から5em、5emから0へ線形的に変形させています。

  • この記事いいね! (1)
著者:杉浦

【Vue】propsで渡ってきたObjectをキーについてバリデーションする

 扱う値があまりに煩雑になってくると系統だった値をまとめてオブジェクト化して扱う様になります。Vue.jsではpropsという仕組みで値をコンポーネント間でやりとりします。基本は次の様に受け渡されるコンポーネント側で型を検査することによって致命的エラーを防ぎます。

  props: {
    memberId: {
      type: Number,
      required: true,
    },
    pageInfo: {
      type: Object,
      required: true,
    },
  },

 この方法を用いた場合、Object型の通過ルールはあまりに緩すぎます。必要なプロパティがそろっていない空のObjectでも通ってしまいます。バリデーションを用いることでこれを防げます。
 プロパティ — Vue.js
 リンク先のカスタムバリデーションがそれです。これはvalidator関数の返す値の真偽によってバリデーションの結果を判定する仕組みです。次のvalidator関数の例は必要なkeyが全てあるかないかの判断になります。

  props: {
    pageInfo: {
      type: Object,
      required: true,
      validator(value) {
        const valueKeys = Object.keys(value);
        let isIncludeAllNeedKey = true;
        ['currentPage', 'from', 'to', 'total', 'lastPage'].forEach((needKey)=>{
          isIncludeAllNeedKey = isIncludeAllNeedKey && valueKeys.includes(needKey);
        });
        return isIncludeAllNeedKey;
      },
    },
  },

 与えられた値valueのキーを配列化、必要なキーについて一つ一つincludes関数で存在の有無を確かめます。どこかで無いとわかった時、isIncludeAllNeedKey = isIncludeAllNeedKey && falseとなり、isIncludeAllNeedKey===falseの状態となり、isIncludeAllNeedKey = false && valueKeys.includes(needKey);が実行されるようになります。これでオブジェクトのキーの有無を確かめるための簡単なバリデータを作れます。

  • この記事いいね! (0)
著者:杉浦

【Laravel】【Vue】propsを使わずVue上で組んだページ部にLaravelのBladeから値を表示する

 LaravelからVueに値を渡す時によくやる手法は次です。

        <upload-image
            v-bind:dist-url="{{json_encode(route('hoge.update'))}}"
            v-bind:hoge="{{json_encode($hoge)}}"
            v-bind:fuga="{{json_encode($fuga)}}"
            ...
            ...
        ></upload-image>
<template>
  <div>
    {{ hoge }}
    {{ fuga }}
    ...
    ...
  </div>
</template>
export default {
  props: {
    distUrl: {type: String, default: ''},
    hoge: {type: String, required: true},
    fuga: {type: String, required: true},
    ...
    ...
  },

 引数をjson化してpropsとして渡し、Vue内のテンプレートに埋め込みます。やり方はシンプルなのですが情報が増えるにつれpropsの肥大化がひどいことになります。なんらかのオブジェクトにまとめるのも手ですが、それはそれでバリデーションが辛くなります。この問題の回答の一つがVueのslotの仕組みを使うことです。
スロット — Vue.js
 slotは指定された位置のHTMLコードをVueコンポーネントを呼び出す階層から決定するための仕組みです。そのため先ほどの例ならば次の様に書けます。

        <upload-image
            v-bind:dist-url="{{json_encode(route('hoge.update'))}}"
        >
          <div slot="slot-no-namae">
              {{ $hoge }}
              {{ $fuga }}
              ...
              ...
          </div>
        </upload-image>
<template>
  <div>
    <slot name="slot-no-namae"/>
  </div>
</template>
export default {
  props: {
    distUrl: {type: String, default: ''},
  },

 Blade上のHTMLエスケープエコーの短縮構文{{}}を実行してHTMLコードを生成。それのHTMLコードをslotに割り当てます。これにより、propsを介することなくPHP内部で扱っていた変数の値を持つHTMLコードをVue上で組んでwebページのテンプレート内に組み込めました。propした値を後から変更しない、Vue内の関数で参照する予定が無い、といった値は大体この方法に組み込むとスッキリしつつ問題のないコードを作るのに貢献してくれます。

  • この記事いいね! (1)
著者:杉浦

Chromeのtextareaでスクロール位置の変更が上手くいかないことがある

 次のデモはVueを使ったtextarea中のクリック、キーアップ、キーダウンに反応してscrollTopを取得、表示するデモです。
 
 これを動かすと少々直感的でない動作とそのscrollTopの値が現れます。例えば次です。デモはclick, key up, key downそれぞれについていますが動画はclick, key downをまとめた一つのみです。

 screenTop:339になってからEnterを押して改行を実行すると、textareaのカーソルが画面内のてっぺんになり、screenTopが更新されません。どういうわけか度々この現象が起きます。マウスでスクロールぐりぐり、上下に余裕のある場所へクリックでカーソル配置、改行で度々再現します。
 これに近い正常な現象は次の様にすると見えます。カーソルをセット、画面外に追いやってからEnterで改行をすると、textareaのカーソルがtextarea内のてっぺんになります。こちらの場合はscrollTopが更新されます。

 この二つから推測されるのは、「何らかの原因で期待するscrollTopがとれず、仮にscrollTop=0と扱って改行を実行。正常な動作同様にtextareaのカーソルが画面内のてっぺんになるようにscrollTop変更。」という動作です。Chrome自体のコードは非公開ですし、詳しい内容はわかりませんがなかなか面白い現象です。
 余談ですがChromeのscrollTopは少数以下も取っていますがFireFox, Microsoft Edgeは少数以下を切り捨ててInt型の様に振る舞っていました。また、Operaは少数ありでした。加えてOperaもカーソルの挙動が不穏でした。少数絡みで何かしらあるのかもしれませんね。

  • この記事いいね! (0)
著者:杉浦

【Vue.js】Vue.jsに関する色々が置かれているリポジトリawesome-vue

vuejs/awesome-vue: 🎉 A curated list of awesome things related to Vue.js
 awesome-vueはVue.jsに関する様々なモノのリストが置かれたリポジトリです。ライブラリはもちろん、教材Vue.jsが使用されたプロジェクトもあります。
 awesome-vueのオーナーは開発の元締めであるvuejsそのものです。そのためか現在もとても活発にリストが更新されており、その多くが有用です。(草の根ライブラリやスニペットを探すと無視できないバグを持っていることがままあります)

 リンクと概要のみを大雑把な分け方で1ページにずらっと並べているの探すのは気持ち手間ですがリンク先は2000を優に超えており、とりあえず何か探したい時に見てみる場所として満足に使えます。

  • この記事いいね! (0)
著者:杉浦

【JavaScript】フレームワーク環境付きオンラインエディタCodeSandbox

CodeSandbox: Online Code Editor Tailored for Web Application Development
 CodeSandboxはJsFiddleなどと同じJavaScript用のオンラインエディタです。CodeSandboxの特徴は次の画像の様にフレームワークを使うことを前提とした部分です。

 上画像にある様にJavaScriptのフレームワークを広くカバーしており、サーバサイド用のフィドルにもなれます。

 自分はよくVue.jsの単一ファイルコンポーネントを使うのでJSFiddleでVue.jsを操るのは少々苦手でしたが、CodeSandboxならば簡単です。一度に複数のファイルを扱えるため、単一ファイルコンポーネントを複数集めて一つのページを作る手法がそのまま使えます。

 青色のShareボタンからコードを見せるためのリンクとHTMLコードを発行することもできます。

  • この記事いいね! (0)
著者:杉浦

【Vue.js】連なった単一ファイルコンポーネント間のイベント発火

 Vue.jsはwebページを部品単位で構築することによって開発を支えるJavaScriptのフレームワークです。
Vue.js
 Vue.jsには単一ファイルコンポーネントという仕組みがあります。これは次のコードの様にあるwebページの部品のHTML、JavaScript、CSSを一ファイルにまとめる仕組みです。

<template>
  <p>{{ greeting }} World!</p>
</template>

<script>
module.exports = {
  data: function () {
    return {
      greeting: 'Hello'
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

単一ファイルコンポーネント — Vue.js
 単一ファイルコンポーネントを用いるとDOMツリーならぬコンポーネントツリーともいうべき構造のコードを記述することになります。下図はあるページのコンポーネントの階層構造です。まとめ役のmain-template、その下に検索ボックスのsearch-box-templateと各モーダル、それぞれに入力欄のform-group-row-inputが連なっています。

 これはまだシンプルに保ててますが、モノによってはもっと深い構造なることもあります。
 離れた場所にある異なるメニュー内に共通アクションのボタンをつけるなど、異なるコンポーネント内で定義されているイベントを発火したい時があります。そういった時は$emitで親のイベントを発火することができます。
API — Vue.js#vm-emit
 サンプルコードなどでもよく現れ、よく使う形はmethod内部での$emitです。次の様に子ファイルの内部で$emit(‘イベント名’,引数)とすると、親ファイル中でhoge-eventが動き、$emitで渡された引数が格納された$event付きのhogeEventAction()が動作します。

// 親ファイル
<first-template
  @hoge-event="hogeEventAction($event)"
/>


// 子ファイル
method{
  fuga(){
    // 何か色々処理
    this.$emit('hoge-event', 'hogehoge');
  }
}

 これがよくある書き方ですが孫の様な直下よりも下にあるコンポーネントから親のイベントを発火する際には冗長です。

// 親ファイル
<first-template
  @hoge-event="hogeEventAction($event)"
/>


// 子ファイル
<second-template
  @hoge-event="hogeEventAction($event)"
/>
// ---省略---
method{
  hogeEventAction(argv){
    this.$emit('hoge-event', argv);
  }
}


// 孫ファイル
method{
  fuga(){
    this.$emit('hoge-event', 'hogehoge');
  }
}

 イベントの定義時にはイベントに対応する関数を記述するのがよくあるパターンですが、この部分はワンライナーならば何でも入れられます。このため中間になるコンポーネントには次の子ファイルの様に、親のイベントを発火する親と同名のイベントを定義すると簡潔なコードになります。

// 親ファイル
<first-template
  @hoge-event="hogeEventAction($event)"
/>


// 子ファイル
<second-template
  @hoge-event="$emit('hoge-event', $event)"
/>


// 孫ファイル
method{
  fuga(){
    this.$emit('hoge-event', 'hogehoge');
  }
}

 子ファイルの内部を汚さずに孫から親のイベントを発火できました。

  • この記事いいね! (0)
著者:杉浦

【Vue.js】デフォルト値による簡易な引数で呼び出せる柔軟なコンポーネント

 Vue.jsではしばしばカスタムHTMLタグとも言える程、小さな部品を作りたくなります。例えばform中の入力欄です。簡易に作るのであれば次の様にlabelとinputに必須属性とユーザ向けの多少の文言をつけるのみです。

<label for="login_username">ユーザ名</label>
<input
  id="login_username"
  type="text"
  name="username">

 実際にはこれにデザインが加わります。

<div class="row">
  <div class="col-md-12">
    <div class="form-group row">
      <label
        class="col-md-3 col-form-label text-md-right"
        for="login_username">ユーザ名</label>
      <input
        id="login_username"
        type="text"
        class="col-md-9 custom-control-input"
        name="username">
    </div>
  </div>
</div>

 Vue.jsを用いるならば、inputタグのvalueと何かしらの変数を同期させたくなるでしょう。動的なバリデーション、警告を追加もするでしょう。

<div class="row">
  <div class="col-md-12">
    <div class="form-group row">
      <label
        class="col-md-3 col-form-label text-md-right"
        for="login_username">ユーザ名</label>
      <div class="col-md-9">
        <input
          id="login_username"
          v-model="vModelValue"
          type="text"
          class="custom-control-input"
          name="username"
          @input="validation()">
       <div
          v-show="isValidationErr"
          class="text-black-50">{{ localValidationErrMsg }}</div>
      </div>
    </div>
  </div>
</div>

 シンプルなtype=”text”でこれです。inputタグの属性に凝ると更にvalue、placeholder、required、typeがnumberならばstep、max、minなどが増えます。これはたかだか一つの入力欄でありフォーム全体を考えた場合、inputタグセットが十個近く並んだりもします。素のHTMLコードのままその様なものを記述した場合、可読性が落ちてしまい開発も保守もやり難くなってしまいます。Vue.jsでコンポーネントとしてまとめることでとりあえずの解決を図ります。

 上のJSFiddleでコンポーネントを用いているHTMLコードは18行程です。実際はコンポーネント定義をHTMLコードと別の1ファイルにまとめるため、コンポーネント定義部はカウントしていません。上述した20行程のinputタグ周りを重ねて書くよりよっぽど楽になりました。
 コンポーネントを用いることで記述は楽になりましたが、全てtype=”text”で様々な属性が全くないinputタグセットでしかなく、柔軟性が犠牲になっています。属性を指定するための引数を増やすことによってもっと柔軟なコンポーネントにします。柔軟にするうえで気を付けることに、呼び出し側で不要なコードが増えない様することがあります。コンポーネントを呼び出すための引数にデフォルト値を与えることによってこれを実現します。

 inputタグの効果が多彩になりました。メールアドレスは\や日本語の様なメールアドレスに含まれることのない文字を打つと、その文字を消して警告を表示します。コードのHTMLを見て頂けばわかる通り引数がinputタグの属性の大部分をカバーする様になっています。それだけ大量の引数が用意されているにもかかわらず、コンポーネント呼び出しが短くまとまっているのがデフォルト値の効果です。

<input
 :value="value"
>
/** 省略 */
props:{
  value: {type: String, default: null},
}

の様に記述するとコンポーネント呼び出し時に引数valueを指定しない場合、value=nullとなりHTMLコード上でもvalue属性は指定されなかったことになります。これと同じ様ことを他の属性にも適用することで大量の引数を用意しつつも、呼び出しを簡単にしています。
 実際のコーディングではンポーネントの定義を1ファイルにまとめる単一ファイルコンポーネントの仕組みを用います。これにより、JSFiddleで表示しているなかなかリッチな画面を作るHTMLコードは次の様にまとまります。

<div id="app">
  <form-input
    label="ユーザ名"
    id="edit_username"
    name="username"
    placeholder="例:浜松 太郎"></form-input>
  <form-input
    label="メールアドレス"
    id="edit_mail"
    name="mail"
    type="mail"
    validation-template="mail"></form-input>
  <form-input
    label="番号"
    id="edit_number"
    name="number"
    type="number"
    value="0.53"
    max="1" min="0" step="0.01"></form-input>
  <form-input
    label="色"
    id="edit_color"
    name="color"
    type="color"
    value="#F0115F"></form-input>
</div>
  • この記事いいね! (0)