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

Vue.js の基本的な機能を使ったサンプルを書く

概要

  • Mustache 構文、条件付きレンダリング、メソッド、算出プロパティ、フォーム入力バインディング、リストレンダリング、コンポーネントの機能を使ったサンプルコードを示す
  • 環境: Vue.js 2.6.11

Mustache 構文で Hello World

  • 画面に Hello, world! と表示するサンプルコード
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello, world!</title>
</head>
<body>

<div id="app">
  <!-- Mustache 構文で message プロパティを表示 -->
  <p>{{ message }}</p>
</div>

<!-- デバッグに便利な Vue.js の開発バージョンを使う -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
// Vue インスタンスを生成
new Vue({

  // Vue インスタンスが管理する DOM 要素
  el: '#app',

  // プロパティ
  data: {
    message: 'Hello, world!'
  }
})
</script>

</body>
</html>

テンプレート構文 — Vue.js

データバインディングのもっとも基本的な形は、”Mustache” 構文(二重中括弧)を利用したテキスト展開です:

条件付きレンダリング、メソッド、算出プロパティ

  • 「カウントアップ」ボタンを押すとカウント数が1増える
  • カウント数を表示する
  • 3の倍数のときはカウント数ではなく「あほー」と表示する
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Counter</title>
</head>
<body>

<div id="myCounterDiv">

  <!-- DOM イベントをトリガーに設定 -->
  <button v-on:click="myCountUp">カウントアップ</button>

  <!-- データバインディング -->
  <!-- v-bind で title 属性にセット -->
  <!-- Mustache 構文で要素内に表示 -->
  <p v-bind:title="myMessage">カウンター: {{ myMessage }}</p>

  <!-- 条件付き描画 (Conditional Rendering) -->
  <p v-if="myCounter % 6 == 0">6の倍数ですね</p>
  <p v-else-if="myCounter % 3 == 0">3の倍数ですね</p>
  <p v-else-if="myCounter % 2 == 0">2の倍数ですね</p>
  <p v-else>2の倍数でも3の倍数でもないですね</p>
</div>

<!-- サイズと速度が最適化された Vue.js の本番バージョンを使う -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

<script>
// Vue インスタンスを生成
var vm = new Vue({

  // Vue インスタンスが管理する DOM 要素
  el: '#myCounterDiv',

  // プロパティ
  data: {
    myCounter: 0
  },

  // created フック
  // インスタンスが作成された後に同期的に呼ばれる
  created: function() {
    this.myCounter = 1
  },

  // メソッド
  methods: {
    myCountUp: function (event) {
      this.myCounter++
    }
  },

  // 算出プロパティ (computed properties)
  computed: {
    myMessage: function () {
      if (this.myCounter % 3 == 0) {
        return "あほー" // 3の倍数のときに返す値
      } else {
        return this.myCounter
      }
    }
  }
})

</script>
</body>
</html>

イベントハンドリング — Vue.js

v-on ディレクティブを使うことで、DOM イベントの購読、イベント発火時の JavaScript の実行が可能になります。

API — Vue.js

v-bind

1つ以上の属性またはコンポーネントのプロパティと式を動的に束縛します。

条件付きレンダリング — Vue.js

v-if ディレクティブは、ブロックを条件に応じて描画したい場合に使用されます。ブロックは、ディレクティブの式が真を返す場合のみ描画されます。

Vue インスタンス — Vue.js

Vue インスタンスが作成されると、自身の data オブジェクトの全てのプロパティをリアクティブシステムに追加します。これらのプロパティの値を変更すると、ビューが”反応”し、新しい値に一致するように更新します。

Vue インスタンス — Vue.js

各 Vue インスタンスは、生成時に一連の初期化を行います。例えば、データの監視のセットアップやテンプレートのコンパイル、DOM へのインスタンスのマウント、データが変化したときの DOM の更新などがあります。その初期化の過程で、特定の段階でユーザー自身のコードを追加する、いくつかの ライフサイクルフック(lifecycle hooks) と呼ばれる関数を実行します。

例えば、created フックはインスタンスが生成された後にコードを実行したいときに使われます。

算出プロパティとウォッチャ — Vue.js

算出プロパティの代わりに、同じような関数をメソッドとして定義することも可能です。最終的には、2つのアプローチは完全に同じ結果になります。しかしながら、算出プロパティはリアクティブな依存関係にもとづきキャッシュされるという違いがあります。

フォーム入力バインディング、リストレンダリング

  • フォームに入力したテキストをリストに追加
  • リストを表示
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>List</title>
</head>
<body>

<div id="myListDiv">

  <!-- フォーム入力バインディング -->
  <input v-model="myItem" placeholder="アイテム名">

  <!-- DOM イベントをトリガーに設定 -->
  <button v-on:click="myAddItem">{{ myItem }} を追加</button>

  <!-- 配列を表示 -->
  <ul>
    <li v-for="item in myItemList">
      {{ item.name }}
    </li>
  </ul>

</div>

<!-- Vue.js のバージョン 2.6.11 を使う -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>

<script>
var vm = new Vue({

  // Vue インスタンスが管理する DOM 要素
  el: '#myListDiv',

  // データ
  data: {
    myItem: '',
    myItemList: [
      { name: 'やくそう' },
      { name: 'どくけーしー' }
    ]
  },

  // メソッド
  methods: {
    myAddItem: function() {
      if (this.myItem !== '') {
        this.myItemList.push({name: this.myItem})
        this.myItem = ''
      }
    }
  },
})

</script>
</body>
</html>

フォーム入力バインディング — Vue.js

form の input 要素 や textarea 要素、 select 要素に双方向 (two-way) データバインディングを作成するには、v-model ディレクティブを使用することができます。

リストレンダリング — Vue.js

配列に基づいて、アイテムのリストを描画するために、v-for ディレクティブを使用することができます。v-for ディレクティブは item in items の形式で特別な構文を要求し、items はソースデータの配列で、item は配列要素がその上で反復されているエイリアスです:

コンポーネント

  • それぞれ別のカウント値を持ったカウンター
  • ボタンを押すとカウント数が1増える
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Counters by Components</title>
</head>
<body>

<div id="components-demo">
  <!-- それぞれが別のインスタンスになるため、それぞれ別の count プロパティを保持する -->
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

<!-- デバッグに便利な Vue.js の開発バージョンを使う -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>

// button-counter という新しいコンポーネントを定義
// コンポーネントは再利用可能な Vue インスタンス
Vue.component('button-counter', {

  // コンポーネントの data オプションは関数でなければならない
  data: function () {
    return {
      count: 0
    }
  },

  // Vue インスタンスに対して使用するテンプレート文字列
  // クリックするとカウントアップ count++
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

new Vue({
  // Vue インスタンスが管理する DOM 要素
  el: '#components-demo',
})

</script>
</body>
</html>

コンポーネントの基本 — Vue.js

コンポーネントは再利用可能な Vue インスタンスなので、data、computed、watch、methods、ライフサイクルフックなどの new Vue と同じオプションを受け入れます。唯一の例外は el のようなルート固有のオプションです。

コンポーネントのプロパティ

  • 表示情報のデータをコンポーネントに渡す
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blog Posts by Components</title>
</head>
<body>

<div id="components-demo">
  <!-- 定義した blog-post コンポーネントを表示 -->
  <!-- v-bind:key で仮想 DOM 処理ヒント用にユニークなキーである myPost.id を指定 -->
  <!-- blog-post の post 属性に値 myPost を渡す -->
  <blog-post
    v-for="myPost in myPostList"
    v-bind:key="myPost.id"
    v-bind:post="myPost"
  ></blog-post>
</div>

<!-- デバッグに便利な Vue.js の開発バージョンを使う -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>

// blog-post という新しいコンポーネントを定義
// コンポーネントは再利用可能な Vue インスタンス
Vue.component('blog-post', {

  // データを受け取るためのプロパティ
  props: ['post'],

  // HTML 描画用テンプレート
  // Mustache 構文でタイトルを出力
  // v-html で HTML をそのまま出力
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <div v-html="post.content"></div>
    </div>
  `
})

new Vue({
  el: '#components-demo',
  data: {
    myPostList: [
      { id: 1, title: 'たいとる1', content: '<p>なかみ1</p>' },
      { id: 2, title: 'たいとる2', content: '<p>なかみ2</p>' },
      { id: 3, title: '>_<;', content: '<p>こうですか!? わかりません><</p>' }
    ]
  }
})

</script>
</body>
</html>

コンポーネントの基本 — Vue.js

プロパティはコンポーネントに登録できるカスタム属性です。値がプロパティ属性に渡されると、そのコンポーネントインスタンスのプロパティになります。

参考資料

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

Blocklyに基づきビジュアルプログラミングの入門級の実例(三、VUE環境でBlocklyのコードを非同期的に処理するサンプル)

本章では、VUEのシンプルなサンプルで、BlocklyのBlockから生成コードを非同期的に実行する方法を説明しております。
Blocklyのコードをステップ毎に実行する時は、Interpreterライブラリをよく使っているんですけど、Interpreterライブラリを使いたくない場合は、JavaScriptのeval関数でBlocklyのコードを実行することも可能です。
evalは一般的に同期でソースコードを実行されているんで、非同期的に実行したい場合はどうしたらよいかについてのことを、以下のサンプルで説明して見ましょう。

事前準備

Blockly

公式サイト:https://developers.google.com/blockly/
Web版の資材:https://developers.google.com/blockly/guides/get-started/web#get_the_code

VUE

https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue.min.js

サンプル

testblock.js

このファイルで、画面表示用のブロックを作成します
BlocklyのDevelopToolから生成することができます。

Blockly.Blocks['block_asyncplay'] = {
  init: function() {
    this.appendStatementInput("MUSIC")
        .setCheck(null)
        .setAlign(Blockly.ALIGN_RIGHT)
        .appendField("play");
    this.setInputsInline(true);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(120);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};
Blockly.JavaScript['block_asyncplay'] = function(block) {
  var statements_music = Blockly.JavaScript.statementToCode(block, 'MUSIC');
  var code = "async function asyncplay() {\n" + statements_music + "\n}\n";
  return code;
};

Blockly.Blocks['block_play'] = {
  init: function() {
    this.appendDummyInput()
        .setAlign(Blockly.ALIGN_RIGHT)
        .appendField("play");
    this.setInputsInline(true);
    this.setPreviousStatement(true, "String");
    this.setNextStatement(true, "String");
    this.setColour(120);
    this.setTooltip("");
    this.setHelpUrl("");
  }
};
Blockly.JavaScript['block_play'] = function(block) {
    var code = "await play(1000);\n";
    return code;
};

test01.html

画面にブロックを表示するためのHTMLファイルです。

<script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue.min.js'></script><!-- 2019-01-25 https://cdnjs.com/libraries/vue -->

    <script src="../blockly-master/blockly_compressed.js"></script>
    <script src="../blockly-master/blocks_compressed.js"></script>
    <script src="../blockly-master/javascript_compressed.js"></script>
    <script src="../blockly-master/msg/js/en.js"></script>
    <script src="./testblock.js"></script>
<body>

<div id="vue_example"></div>

<script>
var vue_example = new Vue({
  el: '#vue_example',

  template: `<div>
        <div width="600px" height="50px">
            <button v-on:click="test()">test</button>
        </div>
        <div width="600px" height="600px">
              <div id="blocklyDiv" style="height: 100%; width: 100%;"></div>
              <xml id="toolbox" ref="toolbox" style="display: none">
                <block type="block_asyncplay"></block>
                <block type="block_play"></block>
              </xml>
              <xml id="workbox" ref="workbox">
                <block type="block_asyncplay" id="id_block_asyncplay" x="10" y="30">
                    <statement name="MUSIC">
                        <block type="block_play" id="id_block_play_01">
                            <next>
                                <block type="block_play" id="id_block_play_02">
                                    <next>
                                        <block type="block_play" id="id_block_play_03"></block>
                                    </next>
                                </block>
                            </next>
                        </block>
                    </statement>
                </block>
              </xml>
        </div>
    </div>`,

  data: {
    message: 'Hello Vue.js!',
  },
  mounted() {
      var workbox = this.$refs["workbox"];
      var options = {
        toolbox: toolbox,
        collapse: true,
        comments: true,
        disable: true,
        maxBlocks: Infinity,
        trashcan: true,
        horizontalLayout: false,
        toolboxPosition: 'start',
        css: true,
        rtl: false,
        scrollbars: true,
        sounds: true,
        oneBasedIndex: true,
        grid: {
          spacing: 20,
          length: 1,
          colour: '#888',
          snap: true
        }
      }

      /* Inject your workspace */
      this.workspace = Blockly.inject('blocklyDiv', options)
      //Workspaceに書かれたBlocksを表示
      Blockly.Xml.domToWorkspace(workbox, this.workspace);

  },
  methods: {
    test: function () {

      var dom = Blockly.Xml.workspaceToDom(this.workspace);
      console.log( Blockly.Xml.domToText(dom));

      var code = Blockly.JavaScript.workspaceToCode(this.workspace);
      console.log( code );

      eval(code);
      asyncplay();

        async function play(waittime) {
            return new Promise((resolve, reject) => {
                console.log("play waittime:" + waittime);
                setTimeout(resolve, waittime, 'time signature');
            });

        }
    },

  },
})
</script>
</body>
</html>

実行結果

実行結果.png

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

【Nuxt.js】 middleware is 何?

ミドルウェアとは

  • ミドルウェアを使用すると、ページがレンダリングされる前(SSR処理などが行われる前)に設定された関数を実行することができる
  • 認証許可が必要なページやログイン後のリダイレクトパスを保持するために使用される。
  • 関数はmiddleware/ディレクトリに入れる
    • middleware/auth.jsauthミドルウェアになる
  • ミドルウェアは第一引数にcontextを取る
    • contextの内容についてはこちらを参照
  • ユニバーサルモードの場合は、サーバーサイドで一度だけ呼び出される。 (Nuxtアプリケーションへの最初のリクエスト時、またはページの再読み込み時)クライアントサイドでは、他のルートへ移動した時に呼び出される。
  • SPAモードの場合、クライアントサイドでの最初のリクエスト時と他のルートへ移動した時に呼び出される。

middlewareを使用した処理の例

storeのアカウント情報が無い場合、ログイン画面にリダイレクトされる処理を書いてみる。

middleware/redirect.js
export default function ({ redirect, store }) {
  const user = store.state.user
  if(!user) {
    redirect('/login')
  }
}

これをnuxt.config.jsで読み込む。

nuxt.config.js
export default {
  router {
    middleware: 'redirect'
  }
}

このようにすると全てのルート変更時にredirect.jsが読み込まれるようになる。

また、特定のページ(またはレイアウト)にのみ特定の関数を設定することもできる。

index.vue
<script>
import { fetchUid } from '@/middleware/uid.js'
export default {
  middleware: [ 'auth', fetchUid ]
}
</script>

1ファイルに1つの処理の場合は、'auth'で関数を実行することができる。
一方、1つのファイルに複数の関数がある場合は、ファイルをimportすることで特定の関数を実行することができる。

middlewareの実行順序について

  • middleware自体の実行順序は、このようになっている。

    • pluginsmiddlewarefetchasyncData
  • middlewareの関数の呼び出し方の違いによる実行順序はこのようになる。

    • nuxt.config.jslayoutpage

Login画面では実行したくない問題

アカウント情報を確認してリダイレクトさせる処理をmiddleware内に書いた場合、Login画面ではまだアカウント情報がないので、関数を実行したくない場合がある。
その場合は、middleware内のcontextrouteがあるので、そこから関数を実行したくないページを弾いて処理を実行させるようにする。

middleware/auth.js
export default function ({ redirect, store, route }) {
  const user = store.state.user
  if(!user && route.path !== '/login') {
    redirect('/login')
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuetify2.xでv-data-tableで行クリックと操作列追加

Vuetify2.xで変更されたV-Data-Tableをカスタマイズするときのメモ

概要

Vuetify1.5から2.2にあげたらV-Data-Tableの書き方が変わっていた。
1.5→2.0にあげた例を参考にさせていただいた。
V-Data-Tableに関しては、リンク先にあるようにカラム指向になったようなイメージでtemplateの書き方がかわったようだった。
最初はV-Data-Iteratorでまわして自力でTableを構築する必要があるのかな、とおもったが公式ドキュメントに例があったので、参照しつつ解決したときのメモ。

行クリック

ドキュメントのAPI欄にさりげなく click:row とあったので試した。

image.png

単にイベントに関数を割り当てるだけでよかった。

抜粋:

vue
<template lang="pug">
  v-data-table(
   :headers="headers"
   :items="db"
   hide-default-footer
   @click:row="clickRow"
  )
</template>
<script>
  export default {
    name: "List",
    data: () => ({
      headers: [
       {text:'ID', value:'id'},
       {text:'なかまたち', value:'name'}
      ],
      db: [
        {id:1, name:'ブタさん'},
        {id:2, name:'ウサギさん'},
        {id:3, name:'キツネさん'},
        {id:4, name:'ゾウさん'}
      ]
    }),
    methods: {
      clickRow(row) {
        console.log(row.name) // 1 => ブタさん...
      }
    }
</script>

1行ごとに操作アイコンなどデータ以外の列を表示

公式ドキュメントの例どおりに

  1. ヘッダー列定義に操作用の列を追加
  2. templateで操作列を定義

ヘッダ列への追加

vueスクリプト抜粋
    data: () => ({
      headers: [
       {text:'ID', value:'id'},
       {text:'なかまたち', value:'name'},
       {text:'操作', value:'action'}
      ],
~~~

表示用template

vueのtemplate
<template lang="pug">
  v-data-table(
   :headers="headers"
   :items="db"
   hide-default-footer
   @click:row="clickRow"
  )
    // 各行・列の描画はよろしくやってくれるので操作列だけ記述すればよい
    template(v-slot:item.action="{item}")
       v-btn(@click="showName(item.name)")
         v-icon fa-home
</template>
<script>
(略)
    showName(str) {
      console.log(str)
    }
(略)
</script>

まとめ

どうかけばいいのか調べるのに時間がかかったけど、結局公式ドキュメントをみて試してなんとかなった。
簡潔にかけるので1.5よりも楽じゃないだろうか。
特定の列だけ表示方法をかえるというのも例のとおりでなんとかなるのだろう。

上記例、それぞれなら問題ないが、行クリックと、操作ボタンを両方設定したいた場合、どちらも同時に実行される部分は未解決(ぐぬぬ)

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

【Nuxt.js】pagination実践編:$router.pushで簡単実装!

前置き

pagination.gif
ページ数に応じて
urlと表示が変わるpaginationです?
前回やった導入編と全く別物です!
こっちの方が簡単なので別パターンとして紹介?

記事タイトルが紛らわしいので、
まとめて名称変えるかもしれません。
こちらの続きはまた別記事にて…!
https://note.com/aliz/n/n8bb439a426a8

firebaseを月曜日に公開予定でしたが、
Cloud Firestoreがバグり。。。
それが落ち着いてからにします☁️
今後は火・木に投稿していく予定です!

構成

・pagination部分をコンポーネント化
・使用するページからpropsでdataを渡す?
・ページ数をurlに表示させる?($router.push)
・全7ページで、ページ数に応じて表示を変更

Step1: コンポーネントでpropsを用意

【構成】
使うコンテンツによって
最大ページ数などが変わるためpropsを使用
・query: ページネーションを使うコンテンツ
・length: ページの長さ
・now: 今いるページ

Pagination.vue
<script>
export default {
 props: {
   query: {
     type: String,
     required: true,
   },
   length: {
     type: Number,
     required: true,
   },
   now: {
     type: Number,
     required: true,
   },
 },
}
</script>

Step2: コンポーネントで戻る・進むボタンを追加

【式】三項演算を使用
式1 ? 式2 : 式3
式1がtrueなら式2、falseなら式3

Pagination.vue
<template>
 <div>
   <button
     class="btn btn-prev"
     @click="$router.push(`?${query}=${now - 1 || 1}`)"
   >
     戻る
   </button>
   <button
     class="btn btn-next"
     @click="$router.push(`?${query}=${now + 1 <= length ? now + 1 : length}`)"
   >
     進む
   </button>
 </div>
</template>

<script>
export default {
 props: {
   query: {
     type: String,
     required: true,
   },
   length: {
     type: Number,
     required: true,
   },
   now: {
     type: Number,
     required: true,
   },
 },
}
</script>

【解説】
◾️戻る

・|| または

該当コンテンツページ内queryの
今いるページnowから1戻る、または1にする

◾️進む
該当コンテンツページ内の
今いるページに1を足して
・全体ページ数と同じ
・またはそれより小さい場合
今いるページから1進む
そうでなければ全体ページ数にする

つまり全体ページを7で、
現在いるページが7なら
7 + 1 <= 7 
falseになるので7のまま
それ以上進むことはないですね?

あれ??
【戻る】すごくシンプルに見えるのに…

Pagination.vue
@click="$router.push(`?${query}=${now - 1 || 1}`)"

【進む】は何か長い。

Pagination.vue
@click="$router.push(`?${query}=${now + 1 <= length ? now + 1 : length}`)"

これではダメなの????

Pagination.vue
@click="$router.push(`?${query}=${now + 1 || length}`)"

最大ページ数を越えてどんどん進みます笑
lengthの値はマイナスにはできません。
そのため制限をかけなくても
勝手に1で止まってくれるのですが…!
プラスは制限をかけないと止まりません?‍♀️?

Step3: コンポーネントでページ数を表示

【構成】
ページ数の表示部分を作りましょう!
・5ページまではページ数分のみ表示
・6ページ以上は…(三点リーダー)で中間を省略

【CSS】
毎度のことですが省きます。
・…はcssでdotクラスでborderを使用
・現在ページがをクラスバインディングで
 background-color, colorを変更?

【if, if, if…】
ifで沢山分岐しています笑
どこで並列になってるか分かりにくいですね?
コンパクトにして全体構造を把握しましょう。

【Pagination.vue】
主にインラインのコメントで解説!
コードでも並列部分を絵文字で区別しています。
?と?が並列で使われている部分です。
それ以外の絵文字は if の目印です!

Pagination.vue
<template>
 <div
   // ページ数が1より大きい、2ページ以上の時のみページネーションを表示
   v-if="length > 1"
   class="list-item list-item-nav"
 >
   <button
     class="btn btn-prev"
     @click="$router.push(`?${query}=${now - 1 || 1}`)"
   >
     戻る
   </button>
   <ul class="list">
     // 1ページ目はどんな時でも固定表示のためif不要
     <li
       // クラスバインディング、{ class名: 式 }でtrueの時にクラスがつく
       :class="{ now: now === 1 }"
       class="item item-link"
       // 1ページを押すとurlが~/1になる
       @click="$router.push(`?${query}=1`)"
     >
       <span class="text">
         1
       </span>
     </li>
     // ?ここから分岐、最大ページ数が2より大きい3〜
     <template v-if="length > 2">
       // ?3以上5以下(=最大ページ数3,4,5の時)
            5ページまでの場合は、最大ページ数に応じて該当ページ数を表示
       <template v-if="length <= 5">
         <li
           :class="{ now: now === 2 }"
           class="item item-link"
           @click="$router.push(`?${query}=2`)"
         >
           <span class="text">
             2
           </span>
         </li>
         // ?最大ページ数が3, 4, 5かつ3より大きい4, 5の時
         <template v-if="length > 3">
           <li
             :class="{ now: now === 3 }"
             class="item item-link"
             @click="$router.push(`?${query}=3`)"
           >
             <span class="text">
               3
             </span>
           </li>
           // ?最大ページ数が3, 4, 5かつ3より大きい4, 5かつ4より大きい5の時
           <template v-if="length > 4">
             <li
               :class="{ now: now === 4 }"
               class="item item-link"
               @click="$router.push(`?${query}=4`)"
             >
               <span class="text">
                 4
               </span>
             </li>
           </template>
         </template>
       </template>
       // ?でなければ(=最大ページが5より大きい6〜)
       <template v-else>
      // ?最大ページ6〜かつ現在いるページが4より少ない(=1, 2, 3の時)
         <template v-if="now < 4">
           <li
             :class="{ now: now === 2 }"
             class="item item-link"
             @click="$router.push(`?${query}=2`)"
           >
             <span class="text">
               2
             </span>
           </li>
           <li
             :class="{ now: now === 3 }"
             class="item item-link"
             @click="$router.push(`?${query}=3`)"
           >
             <span class="text">
               3
             </span>
           </li>
           <li
             // ?現在いるページが4より少ないかつ、3ページ目にいる時
             v-if="now === 3"
             class="item item-link"
             @click="$router.push(`?${query}=4`)"
           >
             <span class="text">
               4
             </span>
           </li>
           <li class="item item-dots">
             <div class="dot" />
             <div class="dot" />
             <div class="dot" />
           </li>
         </template>
         // ?最大ページ6〜かつ現在いるページが1, 2, 3でなく4で〜
         現在いるページに2を出しても最大ページ数と同じか少なければ
         (4ページ目にいるなら4 + 2、最大ページ7の方が大きいためfalse)
              (6ページ目にいるなら6 + 2、最大ページ7より大きいためtrue)
         <template v-else-if="length <= now + 2">
           <li class="item item-dots">
             <div class="dot" />
             <div class="dot" />
             <div class="dot" />
           </li>
           // ?最大ページ数から2を引いた数字が現在いるページだったら
         最大ページ数から3を引いたページ数を表示させる
         (5ページ目にいるなら7-2 =5でtrue、7-3 =4が表示される)
           <li
             v-if="now === length - 2"
             class="item item-link"
             @click="$router.push(`?${query}=${length - 3}`)"
           >
             <span class="text">
               {{ length - 3 }}
             </span>
           </li>
           <li
             :class="{ now: now === length - 2 }"
             class="item item-link"
             @click="$router.push(`?${query}=${length - 2}`)"
           >
             <span class="text">
               {{ length - 2 }}
             </span>
           </li>
           <li
             :class="{ now: now === length - 1 }"
             class="item item-link"
             @click="$router.push(`?${query}=${length - 1}`)"
           >
             <span class="text">
               {{ length - 1 }}
             </span>
           </li>
         </template>
         // ?最大ページ6〜かつ、今までのパターンに該当しない
         (上の?のfalse、現在4ページの場合)
         <template v-else>
           <li class="item item-dots">
             <div class="dot" />
             <div class="dot" />
             <div class="dot" />
           </li>
           <li
             class="item item-link"
             @click="$router.push(`?${query}=${now - 1}`)"
           >
             <span class="text">
               {{ now - 1 }}
             </span>
           </li>
           <li class="item item-link now">
             <span class="text">
               {{ now }}
             </span>
           </li>
           <li
             class="item item-link"
             @click="$router.push(`?${query}=${now + 1}`)"
           >
             <span class="text">
               {{ now + 1 }}
             </span>
           </li>
           <li class="item item-dots">
             <div class="dot" />
             <div class="dot" />
             <div class="dot" />
           </li>
         </template>
       </template>
     </template>
     <li
       :class="{ now: now === length }"
       class="item item-link"
       @click="$router.push(`?${query}=${length}`)"
     >
       <span class="text">
         {{ length }}
       </span>
     </li>
   </ul>
   <button
     class="btn btn-next"
     @click="$router.push(`?${query}=${now + 1 <= length ? now + 1 : length}`)"
   >
     進む
   </button>
 </div>
</template>

<script>
export default {
 props: {
   query: {
     type: String,
     required: true,
   },
   length: {
     type: Number,
     required: true,
   },
   now: {
     type: Number,
     required: true,
   },
 },
}
</script>

これで完成です?

【最大ページ2は?】
if は最大ページ3以上で分岐。
2はどうなっているかというと…
ul 内の構造を黄色い枠で分けています?
・1固定表示
・3ページ以上で分岐
・最大ページ固定表示

2が最大の場合は
最大ページを表示させてるわけです?
Frame 2.png

Step4: コンテンツページでpropsにdataを渡す

【sample.vue】

Pagination.vue
<template>
<div class="page">
  <Pagination
     query="members"
     :length="7"
     :now="Number($route.query.members) || 1"
     class="nav"
   />
</div>
</template>

<script>
import Pagination from '~/components/Pagination.vue'

export default {
 components: {
   Pagination,
 },
 data() {
   return {
     members: [
       {
         name: aLiz
       },
     ],
   }
 },
}
</script>

<style lang="scss" scoped>
</style>

このアカウントでは
Nuxt.js、Vue.jsを誰でも分かるよう、
超簡単に解説しています??
これからも発信していくので、
ぜひフォローしてください♪

https://twitter.com/aLizlab

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

Vue.jsについて基礎的なことを少しまとめました

vue.jsとは

  • データと表示を繋げる仕組みです。
  • MVVM(Model-View-ViewModel)という考え方を元に作られている

MVVM(Model-View-ViewModel)とは

  • Model・・・Vue内に用意したデータ
  • View・・・HTMLで表示している要素
  • ViewModel・・・それらをどのように繋ぐか

MVVMの仕組みを考える際

  1. データは何か
    • web上で変化する部分は何か。そのために必要なデータを考える。
  2. 表示する要素は何か
    • そのデータを、HTMLのどこに、どのように表示するかを考える
  3. どのように繋ぐか
    • HTMLのどこを操作された時にデータがどのように変化するかを考える

という流れで考える

データを作って、表示する要素を用意して、繋ぎ方を決める!

Vue.jsの主な機能

機能 書式
データをそのまま表示する {{データ}}
要素の属性をデータで指定する v-bind
入力フォームとデータを繋げる v-model
イベントと繋ぐ v-on
条件によって表示する v-if
繰り返し表示する v-for
データを使って別の計算をする computed
データの変化を監視する watch
表示/非表示にアニメーションする transition
部品にまとめる component

などなど・・・

参考図書

動かして学ぶ!Vue.js開発入門

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

vue.jsのインスタンスを理解したいと思ったので

vue.jsをふわっと触って動くものを作るなど学習していますが、vue.jsのインスタンスという概念についてあまり理解出来ていないようだったので調べてみました。

まず

  • vue.jsでSPAを作るには、まず、Vueインスタンスを作るところから始める
  • SPAを裏で動かしている実態
vue.js
new Vue ({インスタンスの中身})

または

vue.js
var 変数名 = new Vue({ Vue インスタンスの中身})

という構文で書きます。

インスタンスのなかで、下記のようにずらずらっとオプションというものがあります

(公式の目次のスクショです。)
スクリーンショット 2020-01-21 16.40.56.png

この中で、一番上にある「data」というオプションは、「どんなデータがあるか」を指しています。

書き方は、

vue.js
new Vue ({
  el:"#ID名",
  data:{
     プロパティ:値  // ←データ部分
   プロパティ:   // ←データ部分
    }
  })

という感じで書きます。

dataの「どんなデータがあるか」 の他に、

  • どのHTML要素と繋がるか (el)
  • どんな処理を行うのか (methods)
  • どのデータを使って別の計算をするのか (computed)
  • どのデータを監視するのか (watch)

などの情報を、Vueインスタンス内に書いていきます。

参考図書

動かして学ぶ!Vue.js開発入門

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

既存のRailsアプリにVue.jsを導入する

環境

ruby 2.6.0
Rails 5.2.4.
Vue 2.6.11

gem 'webpacker' のインストール

Gemfile
gem 'webpacker', github: 'rails/webpacker'
bundle

yarn のインストール

$ brew install yarn

$ yarn -v

webpacker のインストール

$ bin/rails webpacker:install
$ bin/webpack

の順に入力。
webpack 関連のファイルが作成・更新される。

Vue.js のインストール

$ rails webpacker:install:vue

このコマンドを実行することで、app/javascript/packs 配下にファイルが作成される。

これでvueの導入は完了。

動作確認

viewの操作をしてみる

app/javascript/packs/hello_vue.js
import Vue from 'vue/dist/vue.esm' #htmlを操作する場合は、 vue/dist/vue.esm をimport

const app = new Vue({
    el: '#hello',
    data: {
      message: "Test text"
    }
})
任意のview
<div id="hello">
  {{ message }}
</div>

<%= javascript_pack_tag 'hello_vue' %>

Test textと表示されればOK。

<%= javascript_pack_tag 'hello_vue' %>でjsファイルの読み込みを行う。

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

花粉症LINE BotからのデータをWEBカレンダーに表示する(花粉カレンダー作成④)

概要

耳鼻咽喉科の開業医をしています。
花粉症の患者さんに使ってもらえるような花粉飛散情報が分かるカレンダーアプリを作りたいと思っています。
これまでカレンダーを表示して予定を入れることと、ユーザー認証の実装、LINEのデータをFirebaseに貯めるところまで行ってきました。
Vue.js×FullCallendarでWEBカレンダー作成(花粉カレンダー作成①)
Auth0で簡単にユーザー認証を実装(花粉カレンダー作成②)
花粉症LINE Botのデータをnode.jsを使ってFirebaseに出し入れする(花粉カレンダー作成③)

今回はLINEBotのデータが記録されているFirebaseのdatabaseのデータをカレンダーに表示することに挑戦しました。

LINEBotの記事はこちら 
花粉症の重症度を判定し自分に合う市販薬を教えてくれるLINE Botの作成

完成動画

https://youtu.be/FKGfKFKBq_U

作成

1.FirebaseのRealtime Databaseの確認

LINEのデータはFirebaseのRealtime Databaseに記録されています。
データは以下のように収納されています。

image.png

今回は以下の情報を取得して重症度や薬剤名、緯度経度をリアルタイムでカレンダーに記入していきたいと思います。
・postback.data(花粉症の重症度や使用している薬剤の情報)
・postback.params.datatime(重症度判定を行った日や薬剤使用開始した日の情報)
・sorce.userID(LINEのユーザーID)
個別の花粉飛散情報を表示するため
・message.latitude(ユーザー位置情報 緯度)
・message.latitude(ユーザー位置情報 経度)

データは.(ドット)で深堀していくことができるようです。

2.実装

以前作成したCalendar.vueに追記していきます。
Vue.js×FullCallendarでWEBカレンダー作成(花粉カレンダー作成①)

methods: { }の中に以下を追記します。
緯度や経度は本当はデータが取得できるだけでいいのですが、今回は本日の日付で表示してみました。

childAdded(snap) {
      const message = snap.val();
      const mes = message.events[0];

      if (mes.type == "postback") {
        console.log(mes.postback.data);
        console.log(mes.postback.params.datetime);
        console.log(mes.source.userId);        

        this.calendarEvents.push({
          title: mes.postback.data,//重症度や薬剤
          start: mes.postback.params.datetime,
          end: mes.postback.params.datetime
        });
      }
      if (mes.type == "message") {        
        if(mes.message.type=="location"){
        console.log(mes.message.latitude);
        console.log(mes.message.longitude);
        userlat = mes.message.latitude;// 緯度
        userlong = mes.message.longitude;//経度
        };      
        this.calendarEvents.push({
          // title: mes.message.text,
          title: `緯度${userlat}`,
          start: "2020-01-19T09:00:00",
          end: "2020-01-19T10:30:00"
        },
        {
          title:`緯度${userlong}`,
          start: "2020-01-19T09:00:00",
          end: "2020-01-19T10:30:00" 
        }
        );
      }
    },

async created() { }の中に以下を追記して完成です。

 const ref_message = firebase.database().ref("protoout/studio/messageList");
 //新しいメッセージ2件だけ表示する
 ref_message.limitToLast(2).on("child_added", this.childAdded);

LINEから位置情報を送ると緯度と経度が表示されます。
image.png

考察

Firebaseのデータをカレンダーに表示することが出来ました。
次は気象APIから花粉情報を表示できるようにしたいと思います。

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

【エラー解消法】Vue.js + Karma + PhantomJS + Firebaseでテストしたときのエラー

掲題の環境で、unitテストを走らせたときのエラーについて解決法を書きます。

実行コマンド

npm run unit

エラー内容

error_log
21 01 2020 10:41:54.753:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/
21 01 2020 10:41:54.757:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
21 01 2020 10:41:54.802:INFO [launcher]: Starting browser PhantomJS
21 01 2020 10:41:57.789:INFO [PhantomJS 2.1.1 (Windows 8.0.0)]: Connected on socket 91jkqAnyQjiCS3D5AAAA with id 48032196
PhantomJS 2.1.1 (Windows 8.0.0) ERROR
  ReferenceError: Can't find variable: Map
  at webpack:///node_modules/@firebase/app/dist/index.cjs.js:301:0 <- index.js:1043

PhantomJS 2.1.1 (Windows 8.0.0): Executed 0 of 0 ERROR (0.729 secs / 0 secs)

解決策

下記の記事を参考に、テストブラウザをPhantomJSからChromeHeadlessに変更しました。
https://dev.oro.com/posts/2017/07/programming/testing-with-headless-chrome/

実行環境

  • Microsoft Windows 10 Home ※仮想環境ではありません
  • node v12.14.1
  • npm v6.13.4
  • package.jsonに記述したunitのスクリプト
    • cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run

エラー内容の解釈

Firebase特有のエラーかと思いましたが、そうではありませんでした。

ReferenceError: Can't find variable: Map の意味がわからなかったので、この言葉だけでググるとStackOverFlowの記事がヒットしました。
https://stackoverflow.com/questions/43803151/referenceerror-cant-find-variable-map

Mapというのは、ES6から導入されたオブジェクトのことだそうです。
https://qiita.com/chihiro/items/9965cd7eca0380cf288c

PhantomJSではサポートされてないオブジェクトのため、エラーとなったというわけです。
なお、polyfillで対応しようとしても別のエラーが出るので、おとなしくChromeHeadlessを使ったほうが良いと思いました。
https://stackoverflow.com/questions/41729482/phantomjs-cant-find-variable-map

なお、karma.conf.jsでframeworksにphantomjs-shimを入れていましたが、PhantomJSを使わなくなったので外しました。

感想

フロントの開発は、環境を理解するだけで時間が掛かります。
しかし、その分だけ、色んなツールが出て対応が早くなっているのも嬉しいことです。

たくさん覚えることがありますが、コツコツ取り組んでいきます。

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

Vue.jsのmountedについて調べました

vue.jsを触る中で出た私の中での新出単語mountedを調べたので書いていきます!!

mountedとは

ライフサイクルフック

・・・mountedは・・・?(´・ω・`)となりますが、まあまあ(ノ´ー`)ノ読んでください

  • vue.jsの初期化の決められたタイミングで実行される関数。初期化中に自動的に実行されるらしいです。

他にも

  • Vueではインスタンスが作られてから、削除されるまでの間で、ライフサイクルフックを実行することができる
  • 初期化の過程で、特定の段階でユーザー自身のコードを追加するために実行する関数

と説明されてました。

ちなみに公式ドキュメントのvue.jsのライフサイクル図は以下のようになっております。
lifecycle.png

ライフサイクルてなんやねん

て、私も最初思ってたんですけど、Vueのライフサイクルを完全に理解された方のQiitaをふむふむと読んで、なんとなく、vueのライフサイクルは、vueの中で実行される流れって感じなのかなと思いました。(語彙力

ということで

私の解釈だとライフサイクルフックは、インスタンス作成時elementへのマウント時データの更新時インスタンスの削除時など、色々やるじゃないですか、その時にのイベント(?)というかタイミング(?)の名前?のことと解釈してます。

ちゅーことで

mountedは、vue.jsのライフサイクルの中でelementにマウントした後、というタイミングの話だったってことです、よね。
なんか間違ってたら教えてください。(´ー`)ノシ

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

Vue.js(Nuxt.js)で画像を表示させる方法(imgタグ、背景画像)

Vue.js(Nuxt.js)で画像を表示させる方法

imgタグとして画像を表示させる

<img src="~/assets/keyvisual.jpg">

背景画像として表示させる

background: url("~@/assets/keyvisual.jpg");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】表示後、3秒経過したら自動的に消えるフラッシュメッセージのサンプルコード

はじめに

表示後、3秒経過したら自動的に消えるフラッシュメッセージのサンプルコードを残します。

※単一ファイルコンポーネントにしていますので、コピペで挙動の確認が可能です。

環境

OS: macOS Catalina 10.15.1
Vue: 2.6.10

結論:コード

※コード内に簡単な説明を記載しています。

Sample.vue
<template>
  <div>

    <!-- v-ifで<script>内のshowtrueであれば表示する -->
    <div
      class="flash"
      v-if="show === true"
    >
      this is flash message!
    </div>

    <!-- clickしたらmethods:{}内のshowFlashが発火する -->
    <button
      @click="showFlash"
    >
      show flash message
    </button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        show: false,
      }
    },

    // Vueインスタンスに変化があったら発動する
    updated() {

      // setTimeoutで3000ms後にshowをfalseにする
      setTimeout(() => {
        this.show = false}
        ,3000
      )
    },
    methods: {
      showFlash(){
        this.show = true;
      }
    }
  }
</script>

<style>
.flash {
  width: 200px;
  height: auto;
}
button {
  width: 200px;
  height: 30px;
  background: red;
  border-radius: 5px;
}
</style>

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

参考にさせて頂いたサイト(いつもありがとうございます)

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

【Vue.js】Vue.Draggableでドラッグ&ドロップ

ドラッグ&ドロップが実装できるライブラリを使ってみたので、メモです。

SortableJS/Vue.Draggable

この記事の目標
  • 項目ドラッグで、リストの入れ替えができるところまで

Image from Gyazo

目次

  1. 導入
  2. 全体
  3. 参考
環境
  • Vue.js 2.6.11

  • Vue.Draggable 2.23.2

1.導入

今回は yarn を使っているので、yarnでインストール

$ yarn add vuedraggable

npm だったら…

$ npm install -g vuedraggable

2.全体

<template>
  <div class="draggable">
    <h6 class="font-weight-bold">ポケモン</h6>
    <draggable element="ul" class="list-group col-4" :list="pokemons">
      <li class="list-group-item" v-for="pokemon in pokemons" :key="pokemon.no">
        {{ pokemon.name }}
      </li>
    </draggable>
  </div>
</template>

<script>
import draggable from 'vuedraggable'

export default {
  components: {
    draggable
  },
  data () {
    return {
      pokemons: [
        { no: '001', name: 'フシギダネ' },
        { no: '004', name: 'ヒトカゲ' },
        { no: '007', name: 'ゼニガメ' },
        { no: '025', name: 'ピカチュウ' }
      ]
    }
  }
}
</script>

<style scoped>
li {
  cursor: pointer;
}
</style>
12行目〜
  • ライブラリを読み込んで、draggableコンポーネントとして使えるようにする
<script>
import draggable from 'vuedraggable'

export default {
  components: {
    draggable
  },
4行目〜
  • draggableのタグで囲んだ部分が、ドラッグで動かせるようになる

    • この例だと、li要素の部分
<draggable element="ul" class="list-group col-4" :list="pokemons">
  <li class="list-group-item" v-for="pokemon in pokemons" :key="pokemon.no">
    {{ pokemon.name }}
  </li>
</draggable>
  • element="ul" 指定しないと<draggable>〜</draggable>がdiv要素になる

    • ↑ ul要素にしたいので、入れています
  • :list="pokemons" この記述が無くても動作はする

    • ↑ ドラッグで項目移動させた状態を配列に反映させたいので、入れています

3.参考

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

Vue.js で 変更したページから遷移する際、編集破棄確認メッセージを出力したい

色々とやっつけですが、書きました。

  • (アンチパターンかと思いますが、)下記コードを mixinとしてまとめて使ってます。
  • ページコンポーネントで、mixinをimportして使ってください。
  • 適したタイミングで addWatchEcentsFor メソッドを呼びます。引数で渡した名前のプロパティの値が変更されたら、フラグが立ちます。
  • フラグが立った状態でページ遷移をしようとすると、「よろしいですか?」のメッセージが出ます。
confirmEditDiscard.js
export default {
  data() {
    return {
      //対象オブジェクトが変更されたか
      isWatchPropChanged: false
    };
  },
  /**
   * 対象オブジェクトの変更監視イベントを追加する。
   */
  methods: {
    addWatchEventsFor(watchPropName) {
      this.$watch(
        watchPropName,
        // 変更があったらフラグを立てる
        function() {
          this.isWatchPropChanged = true;
        },
        { deep: true }
      );
    }
  },
  beforeRouteLeave(to, from, next) {
    // 確認画面へは、チェックせずページ遷移
    const componentName = this.$options.name;
    if (componentName === to.name.replace("Confirm", "")) {
      next();
      return;
    }
    // 変更がない場合も、チェックせずページ遷移
    if (!this.isWatchPropChanged) {
      next();
      return;
    }

    window.confirm("編集データは破棄されます。よろしいですか?")
      ? next()
      : next(false);
  }
};

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