20191128のvue.jsに関する記事は9件です。

Vue.js でマインスイーパっぽいものを作ってみた

?初めに

コーディングの練習のために、個人的に慣れ親しんでいる Vue.js で単純な実装のゲームとされるマインスイーパを作ってみました。

マインスイーパの仕様をきちんと調べたわけではないため、動きに若干不安がありますが、そのあたりは漸次改善していく予定です。

?本題

環境

バージョン
Node v12.11.1
npm 6.11.3
vue-cli 4.0.5

成果物

それっぽい動きしています。

minesweeper.gif

ソースコード(github)

実装のポイント

マインスイーパーの各マスを作る

          <template v-for="(cell, yi) in row">
            <td
              v-if="started"
              :key="yi"
              class="cell cell--started"
              :class="{ 'cell--opened': cell.isOpen }"
              @click="open(xi, yi)"
              @click.right.prevent="mark(xi, yi)"
            >
              <span v-if="cell.isOpen">
                {{ cell.isBomb ? "?" : cell.bombCount === 0 ? "" : cell.bombCount }}
              </span>
              <span v-if="cell.isMark">
                ?
              </span>
            </td>
            <td
              v-if="!started"
              class="cell cell--unstarted"
              :key="yi"
            >
              &nbsp;
            </td>

マインスイーパーの盤面自体は table 要素で作っています。
地雷が配置される各マスは、td 要素で表現しています。
v-if によりフラグを切り替えることで、以下の状態を切り替えています。

  • そもそもゲームが開始されているかどうか
  • マスが開かれているかどうか
  • 旗が立てられているかどうか

また以下のように、マスをクリックすることで、マスを開いたり旗を立てたりする処理を発火させるようにしています。

  • 左クリック ... マスを開く処理を発火する
  • 右クリック ... 旗を立てる処理を発火する

地雷を配置する

    /**
     * 地雷を配置します.
     */
    _layMines() {
      // ランダムな整数値を返す
      const getRandomInt = max => {
        return Math.floor(Math.random() * Math.floor(max));
      }
      for (let i = 0; i < this.numOfBomb; i++) {
        const x = getRandomInt(this.maxX);
        const y = getRandomInt(this.maxY);
        // 既に地雷が置かれている場合、別のセルに置くようにする
        if (this.area[x][y].isBomb) {
          i--;
          continue;
        }
        this.area[x][y].isBomb = true;
        // 地雷に隣接するセルの周囲の地雷数を増やす
        // 上のセル
        if (x-1 >= 0) {
          this.area[x-1][y].bombCount++;
          if (y-1 >= 0) this.area[x-1][y-1].bombCount++;
          if (y+1 < this.maxY) this.area[x-1][y+1].bombCount++;
        }
        // 横のセル
        if (y-1 >= 0) this.area[x][y-1].bombCount++;
        if (y+1 < this.maxY) this.area[x][y+1].bombCount++;
        // 下のセル
        if (x+1 < this.maxX) {
          this.area[x+1][y].bombCount++;
          if (y-1 >= 0) this.area[x+1][y-1].bombCount++;
          if (y+1 < this.maxY) this.area[x+1][y+1].bombCount++;
        }
      }      
    }
  }

設定された地雷の数だけ繰り返して地雷を盤面に設置しています。
ランダムな x 行目・ y 列目を取得し、その x 行 y 列のマスにボムであるフラグを立てます。
フラグを立てた後、周囲8マスの「周囲に地雷がある数」を +1 させています。

マスを開く

    /**
     * セルを開きます.
     * @param {Number} x 何行目.
     * @param {Number} y 何列目.
     */
    open(x, y) {
      // ?だったら終了
      if (this.area[x][y].isBomb) {
        alert("Bomb!!!");
        for (let i = 0; i < this.maxX; i++) {
          for (let j = 0; j < this.maxY; j++) {
            this.area[i][j].isOpen = true;
          }
        }
      }
      this.area[x][y].isOpen = true;
      if (this.area[x][y].bombCount !== 0) return;
      // 周囲に地雷がない空きセルを開く
      // 左のセル
      if (x-1 >= 0 && this.area[x-1][y].autoOpenable()) {
        this.open(x-1, y);
      }
      // 右のセル
      if (x+1 <= this.maxX-1 && this.area[x+1][y].autoOpenable()) {
        this.open(x+1, y);
      }
      // 上のセル
      if (y-1 >= 0 && this.area[x][y-1].autoOpenable()) {
        this.open(x, y-1);
      }
      // 下のセル
      if (y+1 <= this.maxY-1 && this.area[x][y+1].autoOpenable()) {
        this.open(x, y+1);
      }
    },

マスを開く処理では、単にクリックされたマスを開くとともに、「周囲に一つも地雷がない」隣接するマスを再帰的に開いていく処理を行っています。

具体的には、以下のような再帰処理を行っています。
1回目に実行される(A)では、クリックされたマスに対して判定処理を行います。

  1. (A)マスの上下左右を開くことができるか判定する。
    1. 開くことができる場合
      1. マスを開く。
      2. 開いたマスに対して、(A)の処理を行う。
    2. 開くことができない場合
      1. 終了。

マスを開くことができるか判定する処理は、以下の通りです。

  /**
   * 自動で開くことができるか判定して返します.
   * @return {Boolean}
   */
  autoOpenable() {
    return !this.isOpen && !this.isBomb;
  }

旗を立てる

    /**
     * セルに旗を立てたり、立てなかったりします.
     * @param {Number} x 何行目.
     * @param {Number} y 何列目.
     */
    mark(x, y) {
      if (!this.area[x][y].isOpen) {
        this.area[x][y].isMark = !this.area[x][y].isMark;
      }
    },

旗を立てる処理はごく単純で、マスがまだ開かれていない場合、旗を立てるか立てないかを切り替えるようにしています。

?おわりに

今回はマインスイーパ(?)を作りましたが、今後はテトリスやぷよぷよも作ってみたいと思います。
Vue.js で作るのもいいですし、生のJSやSVG、canvasで作るのも面白そうです。

参考文献

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

Vue.js【 transitionを使ってメニューバーを作成 】

はじめに

三ヶ月近く触っているとVue.jsに慣れ始めてきました。進捗は相変わらず遅いですが、、、
そんなわけで以前から作れるようになりたいと思っていたメニューバーを作成していきたいと思います。

動作

ダウンロード.gif

完成コード

htmlファイルにコピペしてもらえば動くはずですので是非試してみてください。

index.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>MENU BAR</title>
  </head>
  <body>
    <div id="app">
      <nav class="entireMenuBar">
              <ul>
                <div class="parentMenuBar1">
                  <li @click="{menuToggle1 = !menuToggle1,menuToggle2 = false}">親要素1</li>
                    <transition>
                      <div class="childMenuBar2" v-show="menuToggle1">
                        <ul>
                          <li>子要素1-1</li>
                          <li>子要素1-2</li>
                          <li>子要素1-3</li>
                        </ul>
                      </div>
                    </transition>
                </div>
              </ul>
              <ul>
                <div class="parentMenuBar2">
                  <li @click="{menuToggle2 = !menuToggle2,menuToggle1 = false}">親要素2</li>
                    <transition>
                      <div class="childMenuBar2" v-show="menuToggle2">
                        <ul>
                          <li>子要素2-1</li>
                          <li>子要素2-2</li>
                          <li>子要素2-3</li>
                        </ul>
                      </div>
                    </transition>
                </div>
              </ul>
      </nav>
      <div id="main" @click="{menuToggle2 = false ,menuToggle1 = false}">


      <br><br><br><br><br><br>
      <pre>{{ $data }}</pre>

    </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      new Vue({
        data:{
          menuToggle1:false,
          menuToggle2:false,
        }
      }).$mount('#app')
    </script>

  </body>
  <style media="screen">
    .childMenuBar1,.childMenuBar2{
      position:relative;
      margin-top:6px;
      border-radius:2px;
      background-color:lightblue;
      z-index:-1;
    }
    body{
      margin:0;
      padding:0;
      background-color:lightgray;
    }
    ul{
      list-style:none;
      padding:0;
      margin:0;
    }
    li{
      margin:0;
      cursor:pointer;
    }
    .entireMenuBar{
      background-color: #eee;
      padding:5px;
      display:flex;
      height:30px;
    }
    .parentMenuBar1,.parentMenuBar2{
      width:80px;
      padding:5px;
      margin:0px 0px 0px 20px;
      border-radius:5px;
    }

    #main{
      height:100%;
    }
    .v-enter-active,.v-leave-active{
      transition: transform .2s ease-out;
    }

    .v-enter,.v-leave-to {
      transform: translateY(-90px);
    }
  </style>
</html>


トランジションに注目

以下のように書くことでCSSでアニメーションを設定するように要素を動かすことができます。

index.html
.v-enter-active,.v-leave-active{
      transition: transform .2s ease-out;
    }

    .v-enter,.v-leave-to {
      transform: translateY(-90px);
    }

.v-enter-active,.v-leave-active
の中で動きの種類とそれにかける時間を指定しています。
.v-enter,.v-leave-to
の中で動きの開始時と終了時の状態を指定しています。

動きの流れ的には、、、

アニメーション開始時
1,v-enter
2,v-enter-active

アニメーション終了時
3,v-leave-active
4,v-leave-to

といった流れです。
そしてこの動きをつけたい要素を
<transition>動きをつけたい要素</transition>
のようにトランジションタグで囲んであげれば
動画のような動きをしてくれる要素の完成です。

参考:vue.jsリファレンス トランジション

以上です。最後までお読みいただきありがとうございました。

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

ポケモン X Vue

https://www.youtube.com/watch?v=8pFkiNvxnD0

のビデオを見て感動したので記事を書いています。

githubページが載っているので、誰のためでもなくただ自分のために書きます。vueのロジックだけです。

src/componentsの中だけの説明です。

まず、Pokemon.vueには

Pokemon.vue
<template>
  <div class="container">
    <h1>Pokemon's</h1>
    <PokemonSearch 
      :apiUrl="apiUrl" 
      @setPokemonUrl="setPokemonUrl" />
    <PokemonList 
      :imageUrl="imageUrl" 
      :apiUrl="apiUrl"
      @setPokemonUrl="setPokemonUrl" />
    <PokemonDetail 
      v-if="showDetail"
      :pokemonUrl="pokemonUrl"
      :imageUrl="imageUrl"
      @closeDetail="closeDetail" />
  </div>
</template>

<script>
  import PokemonSearch from './PokemonSearch.vue';
  import PokemonList from './PokemonList.vue';
  import PokemonDetail from './PokemonDetail.vue';
  export default {
    data: () => {
      return {
        imageUrl: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/',
        apiUrl: 'https://pokeapi.co/api/v2/pokemon/',
        pokemonUrl: '',
        showDetail: false
      }
    },
    components: {
      PokemonSearch,
      PokemonList,
      PokemonDetail
    },
    methods: {
      setPokemonUrl(url) {
        this.pokemonUrl = url;
        this.showDetail = true;
      },
      closeDetail() {
        this.pokemonUrl = '';
        this.showDetail = false;
      }
    }
  }
</script>

見てわかる通り。

PokemonList.vue
<template>
  <div class="detail">
    <div class="detail-view" v-if="show">
      <div v-if="pokemon" class="image">
        <img :src="imageUrl + pokemon.id + '.png'" alt="">
      </div>
      <div v-if="pokemon" class="data">
        <h2>{{ pokemon.name }}</h2>
        <div class="property">
          <div class="left">Base Experience</div>
          <div class="right">{{ pokemon.base_experience }} XP</div>
        </div>
        <div class="property">
          <div class="left">Height</div>
          <div class="right">{{ pokemon.height / 10 }} m</div>
        </div>
        <div class="property">
          <div class="left">Weight</div>
          <div class="right">{{ pokemon.weight / 10 }} kg</div>
        </div>
        <h3>Pokemon Types</h3>
        <div class="types">
          <div class="type" 
            v-for="(value, index) in pokemon.types"
            :key="'value'+index">
            {{ value.type.name }}
          </div>
        </div>
        <h3>Abilities</h3>
        <div class="abilities">
          <div class="ability" 
            v-for="(value, index) in pokemon.abilities"
            :key="'value'+index">
            {{ value.ability.name }}
          </div>
        </div>
      </div>
      <h2 v-else>The pokemon was not found</h2>
      <button class="close" @click="closeDetail">close</button>
    </div>
    <i v-else class="fas fa-spinner fa-spin"></i>
  </div>
</template>

<script>
  export default {
    props: [
      'pokemonUrl',
      'imageUrl'
    ],
    data: () => {
      return {
        show: false,
        pokemon: {}
      }      
    },
    methods: {
      fetchData() {
        let req = new Request(this.pokemonUrl);
        fetch(req)
          .then((resp) => {
            if(resp.status === 200)
              return resp.json();
          })
          .then((data) => {
            this.pokemon = data;
            this.show = true;
          })
          .catch((error) => {
            console.log(error);
          })
      },
      closeDetail() {
        this.$emit('closeDetail');
      }
    },
    created() {
      this.fetchData();
    }
  }
</script>

このプログラムはポケモンのイラストをリスト状に展開してそれをクリックすると
そのポケモンのデータが表示されているモーダルが
表示されるというプログラムになっています。

つまり、このプログラムは後述するcomponents/PokemonList.vueからデータを渡されて
そのままそのデータを表示する流れとなっています。

methods: {
fetchData() {
let req = new Request(this.pokemonUrl);
fetch(req)
.then((resp) => {
if(resp.status === 200)
return resp.json();
})
.then((data) => {
this.pokemon = data;
this.show = true;
})
.catch((error) => {
console.log(error);
})
},

methodsのfetchData()でデータを取得していることが解ると思います。

PokemonList.vue

<template>
  <div class="list">
    <article v-for="(pokemon, index) in pokemons"
    :key="'poke'+index"
    @click="setPokemonUrl(pokemon.url)">
      <img :src="imageUrl + pokemon.id + '.png'" width="96" height="96" alt="">
      <h3>{{ pokemon.name }}</h3>
    </article>
    <div id="scroll-trigger" ref="infinitescrolltrigger">
      <i class="fas fa-spinner fa-spin"></i>
    </div>
  </div>
</template>

<script>
  export default {
    props: [
      'imageUrl',
      'apiUrl'
    ],
    data: () => {
      return {
        pokemons: [],
        nextUrl: '',
        currentUrl: ''
      }
    },
    methods: {
      fetchData() {
        let req = new Request(this.currentUrl);
        fetch(req)
          .then((resp) => {
            if(resp.status === 200)
              return resp.json();
          })
          .then((data) => {
            this.nextUrl = data.next;
            data.results.forEach(pokemon => {
              pokemon.id = pokemon.url.split('/')
                .filter(function(part) { return !!part }).pop();
              this.pokemons.push(pokemon);
            });
          })
          .catch((error) => {
            console.log(error);
          })
      },
      scrollTrigger() {
        const observer = new IntersectionObserver((entries) => {
          entries.forEach(entry => {
            if(entry.intersectionRatio > 0 && this.nextUrl) {
              this.next();
            }
          });
        });
        observer.observe(this.$refs.infinitescrolltrigger);
      },
      next() {
        this.currentUrl = this.nextUrl;
        this.fetchData();
      },
      setPokemonUrl(url) {
        this.$emit('setPokemonUrl', url);
      }
    },
    created() {
      this.currentUrl = this.apiUrl;
      this.fetchData();
    },
    mounted() {
      this.scrollTrigger();
    }
  }
</script>

まずポケモンのデータをぶん回しているところから

<div class="list">
<article v-for="(pokemon, index) in pokemons"
:key="'poke'+index"
@click="setPokemonUrl(pokemon.url)">
<img :src="imageUrl + pokemon.id + '.png'" width="96" height="96" alt="">
<h3>{{ pokemon.name }}</h3>
</article>
<div id="scroll-trigger" ref="infinitescrolltrigger">
<i class="fas fa-spinner fa-spin"></i>
</div>

methods: {
      fetchData() {
        let req = new Request(this.currentUrl);
        fetch(req)
          .then((resp) => {
            if(resp.status === 200)
              return resp.json();
          })
          .then((data) => {
            this.nextUrl = data.next;
            data.results.forEach(pokemon => {
              pokemon.id = pokemon.url.split('/')
                .filter(function(part) { return !!part }).pop();
              this.pokemons.push(pokemon);
            });
          })
          .catch((error) => {
            console.log(error);
          })
      },

の中fetchData()が面白いので説明すると
```fetch(req)
.then((resp) => {
if(resp.status === 200)
return resp.json();
})
.then((data) => {

        this.nextUrl = data.next;
        data.results.forEach(pokemon => {
          pokemon.id = pokemon.url.split('/')
            .filter(function(part) { return !!part }).pop();
          this.pokemons.push(pokemon);
        });
      })


ポケモンのurlごとのデータを分割して最後の要素を取得しています。
MDNのサイトを参照のこと

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/pop

以上です。
お疲れ様でした。

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

Vue.jsの開発環境をDockerで構築する手順

Vue.jsの開発環境をDockerで構築する手順を整理します。

実際に環境構築した後のソースコードはこちら

事前準備

Dockerで開発環境を構築するために必要な事項を確認します。

Vue CLI

今回は Vue CLI でアプリを作ります。

Vue CLI をインストールしてない場合は、そのインストールからおこないます。(ついでに @vue/cli-init もインストールします。)

sudo npm install -g @vue/cli @vue/cli-init

バージョン確認できれば、正常にインストールできています。

vue -V
@vue/cli 4.0.5

インストール後、webpack-simple でアプリを作成します。

vue init webpack-simple your_app_name

Docker

Docker をインストールしていない場合は、Docker Desktop on Mac もしくは Docker Desktop on Windows 等からインストールしてください。

バージョン確認できれば、正常にインストールできています。

docker version

合わせて docker-compose コマンドが使えることも確認しておきます。

docker-compose --version

開発環境 Docker 化の手順

それでは本題に入り、Vue.jsの開発環境をDockerで構築する手順を整理します。

1. vue-cli-service のインストール

まず Docker コンテナ内で Vue.js を起動するのに必要な vue-cli-service をインストールします。

npm install --save @vue/cli-service

インストール後、package.jsonscripts.serve の設定を追加します。(該当箇所以外は省略してます。)

package.json
{
  "scripts": {
    "serve": "node_modules/.bin/vue-cli-service serve"
  }
}

2. webpack のインストール

アプリ起動時にコンパイルエラーが出ないように、webpack を追加しておきます。

npm add webpack@latest

3. Docker の設定

必要な下準備が整ったので、Docker の設定ファイルを書いていきます。

Dockerfile.dev の作成

アプリのルートディレクトリに Dockerfile.dev を作成します。

Dockerfile.dev
FROM node:10.17.0-alpine3.9

# make the 'app' folder the current working directory
WORKDIR /app

# copy both 'package.json' and 'package-lock.json' (if available)
COPY package*.json ./

# install project dependencies
RUN npm install

# copy project files and folders to the current working directory (i.e. 'app' folder)
COPY . .

CMD ["npm", "run", "serve"]

  • FROM Docker イメージのベース(バージョンは DockerHub で選んでください)
  • WORKDIR ワーキングディレクトリの作成
  • COPY package*.json ./ 依存関係のコピー
  • RUN npm install 依存関係のインストール
  • COPY . . Docker コンテナ内にファイルを渡す
  • CMD ["npm", "run", "serve"] コマンドでサーバ起動

.dockerignore の作成

また、.dockerignore を作成し、無視してOKなファイルを Docker に知らせます。

.dockerignore
node_modules
.git
.gitignore

Docker の設定は以上です。

4. Dockerコンテナ内で起動

コマンドを叩けば、Docker コンテナ内で Vue.js のアプリを起動できます。

docker build

まず、Dockerfile.dev から Docker イメージをビルドします。

docker build --tag your_app_name:latest --file Dockerfile.dev .
  • --tag タグ名(タグをつけて build すれば、その名前で run できる)
  • --file 設定ファイルを指定(デフォルトは Dockerfile

docker run

ビルドした Docker イメージを使ってコンテナを起動します。

docker run --rm -it --name your_app_container -p 8080:8080 -v ${PWD}:/app -v /app/node_modules your_app_name:latest
  • --rm 終了時にコンテナを自動削除
  • -i ターミナルのテキストコマンドをコンテナに入力
  • -t いい感じのフォーマットでターミナルに出力
  • --name コンテナに名前をつける
  • -p ポートマッピング
  • -v Docker ボリューム(ローカルのコードを参照させれば、変更をただちに反映可)
  • $(PWD) present working directory

問題なければ、ブラウザから http://localhost:8080/ でアクセスできます。Yay!

localhost:8080

ターミナルで Ctrl + C でコンテナを終了できます。

docker-compose でより簡単に起動

毎回毎回 docker build...docker run... とコマンドを叩くのは面倒なので、docker-compose を使ってより簡単に起動できるようにします。

docker-compose の設定

docker-compose.yml をルートディレクトリに作成し、Docker コマンドの内容を YML 形式で記述します。

docker-compose.yml
version: '3.7'

services:
  web:
    container_name: web
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - '.:/app'
      - '/app/node_modules'
    ports:
      - '8080:8080'

設定はこれだけです。

docker-compose コマンド

これにより以下のコマンドが使えます。

docker-compose up

コンテナの起動します。

docker-compose up

先ほどと同様にhttp://localhost:8080/ でアクセスできます。

かなりシンプルになりました。

docker-compose up --build

コンテナを再ビルドして起動します(変更を反映させられる)。

docker-compose up --build

docker-compose up -d

コンテナをバックグラウンドで起動します。

docker-compose up -d

docker-compose down

コンテナを終了させます。

docker-compose down

docker-compose にテストを追加

docker-compose.ymltest サービスを追加し、そのコンテナ内でテストを実行できるようにします。

docker-compose.yml
version: '3.7'

services:
  web:
    container_name: web
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - '.:/app'
      - '/app/node_modules'
    ports:
      - '8080:8080'
  test:
    container_name: test
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - '.:/app'
      - '/app/node_modules'
    command: ["npm", "run", "test"]

※ *Vue.js でのテストのセットアップ手順は、「Vue.js ユニットテストの基本まとめ」参照

次のコマンドでテスト用のコンテナサービスを起動できます。

docker-compose up test

サービス名を指定しない場合は、web・test の両サービスが起動します。

docker-compose up

トラブルシューティング

発生する可能性のあるエラーとその解決方法をまとめておきます。

エラー(その1)

エラー内容

docker run 実行時に vue-cli-service がないと怒られる場合。

sh: vue-cli-service: command not found

解決方法

vue-cli-service をインストールすることで解決します。

npm install --save @vue/cli-service

エラー(その2)

エラー内容

docker run 実行時に script: serve がないと怒られる場合。

npm ERR! missing script: serve

解決方法

package.jsonscripts.serve を追加することで解決します。

package.json
{
  "scripts": {
    "serve": "node_modules/.bin/vue-cli-service serve"
  }
}

エラー(その3)

エラー内容

docker run 実行時に compilation.templatesPlugin... のコンパイルエラーが起きる場合。

ERROR  Failed to compile with 1 errors 
  TypeError: compilation.templatesPlugin is not a function

解決方法

Webpack を追加することで解決します。

npm add webpack@latest

エラー(その4)

エラー内容

docker-compose up 実行時にコンテナ名が被っていると怒られる場合。

ERROR: for web  Cannot create container for service web: Conflict. The container name "/web" is already in use by container "f4acb3dbb5". You have to remove (or rename) that container to be able to reuse that name.

解決方法

名前が重複しているコンテナを取り除くことで解決します。

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

vue+気象情報APIでお天気appを作ってみた

自分に対する備忘録という目的で記事を投稿させていただきます。
(試験的に限定共有ではなく本投稿させていただきます)

vueバージョン:vue2.5.16
使用するAPI:OpenWeatherMap

初期状態

index.htmlとmain.jsを用意
またOpenWeatherMapを用いるにはAPIキーが必要になるためあらかじめ登録を済ませてAPIキーを参照できるようにすること

index.html

<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>WeatherApp</title>
  </head>
  <body>
    <div id="app">
      {{ message }}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

main.js

var app = new Vue({
  el: '#app',
  data: {
    message: "Hello Vue.js!"
  },
})

htmlファイルを開き、"Hello Vue.js!"と表示されればひとまずOK

APIからデータを取得する

mounted:...でページが読み込まれた時の記述を追加する

main.js

var app = new Vue({
  el: '#app',
  data: {
    message: "Hello Vue.js!"
  },
+ mounted: function(){
+   axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${"Tokyo"},jp&units=metric&appid=APIキー`)
+   .then(function(response){
+     console.log(response)
+   })
+   .catch(function(error){
+     console.log(error)
+   })
+ },
})

axios.getのところでAPIを叩く
q=Tokyo,q=Osakaのように書くことで気象情報を取り出す場所を指定することができる。
今回は後から変数を用いるので変数展開で書く。
"appid="の後にAPIキーを記述すること。

htmlファイルを開きconsole画面を見ると現在の気象情報データをとってこれているはず。

スクリーンショット 2019-11-27 19.34.49.png

data/name:場所
data/weather[0]/main:現在の天気
が格納されているのでこれらを画面に表示するようにする

天気を表示させる

main.js

var app = new Vue({
  el: '#app',
  data: {
+   city: null, 
+   condition:null,
  },
  mounted: function(){
    axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${"Tokyo"},jp&units=metric&appid=APIキー`)
    .then(function(response){
+     this.city = response.data.name
+     this.condition = response.data.weather[0].main
+   }.bind(this))
    .catch(function(error){
      console.log(error)
    })
  },
})

index.html

<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>WeatherApp</title>
  </head>
  <body>
    <div id="app">
+     <div class="weather_info">
+       <p class="weather_city">{{ city }}</p>
+       <p class="weather_condition">{{ condition }}</p>
+     </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

これでindex.htmlを開くと場所名(Tokyo)と現在の天気が表示されている筈。

さらにここから現在の気温を表示させてみる。
(気温は先ほどのconsole画面の中のdata/main/tempに格納されている)

main.js

var app = new Vue({
  el: '#app',
  data: {
    city: null, 
+   temp: null, 
    condition:null,
  },
  mounted: function(){
    axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${"Tokyo"},jp&units=metric&appid=APIキー`)
    .then(function(response){
      this.city = response.data.name
      this.condition = response.data.weather[0].main
+     this.temp = response.data.main.temp
    }.bind(this))
    .catch(function(error){
      console.log(error)
    })
  },
+ filters: {
+   roundUp(value){
+     return Math.ceil(value)
+   }
+ }
})



index.html

<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>WeatherApp</title>
  </head>
  <body>
    <div id="app">
      <div class="weather_info">
        <p class="weather_city">{{ city }}</p>
        <p class="weather_condition">{{ condition }}</p>
+       <p class="weather_temp">{{ temp | roundUp }}&deg;C</p>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

気温は少数も含まれているので
今回はfiltersオプションを用いて小数点切り上げして出力するようにしている。
これでindex.htmlを開くと以下のように表示される

スクリーンショット 2019-11-27 20.06.24.png

気象データの場所を選択できるようにする

今の状態だとTokyoの気象情報しか持ってこれないので
場所を選択できるようにしてみる。

main.js

var app = new Vue({
  el: '#app',
  data: {
    city: null, 
    temp: null, 
    condition:null,
+   selectedCity: '',
  },
+ methods:{
+   select: function() {
      axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${this.selectedCity},jp&units=metric&appid=APIキー`)
      .then(function(response){
        this.city = response.data.name
        this.temp = response.data.main.temp
        this.condition = response.data.weather[0].main
      }.bind(this))
      .catch(function(error){
        console.log(error)
      })
    }
  },
  filters: {
    roundUp(value){
      return Math.ceil(value)
    }
  }
})

index.html

<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>WeatherApp</title>
  </head>
  <body>
    <div id="app">
      <div class="weather_info">
        <p class="weather_city">{{ city }}</p>
        <p class="weather_condition">{{ condition }}</p>
        <p class="weather_temp">{{ temp | roundUp }}&deg;C</p>
      </div>
+     <select v-model="selectedCity" v-on:change="select">
+       <option disabled value="">Please select one</option>
+       <option>Tokyo</option>
+       <option>Osaka</option>
+       <option>Yokohama</option>
+       <option>Sapporo</option>
+     </select>
+     <span>Selected: {{ selectedCity }}</span>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

v-model="selectedCity"
でselectedCityの値を双方向データバインディングさせる。
axios.getでselectedCityを指定しているので、このselectedCityの値が気象情報を取得する場所となる。
v-on:change="select"と書かれている通り、選択ボックスをクリックするとAPIを叩くようになっている。

index.htmlを開くとこのように動く筈。

test.gif

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

vue+気象情報APIで簡易お天気appを作ってみた

自分に対する備忘録という目的で記事を投稿させていただきます。
(試験的に限定共有ではなく本投稿させていただきます)

vueバージョン:vue2.5.16
使用するAPI:OpenWeatherMap

参考文献

この記事は以下の情報を参考にして執筆しました。
https://www.tam-tam.co.jp/tipsnote/html_css/post16405.html

初期状態

index.htmlとmain.jsを用意
またOpenWeatherMapを用いるにはAPIキーが必要になるためあらかじめ登録を済ませてAPIキーを参照できるようにすること

index.html

<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>WeatherApp</title>
  </head>
  <body>
    <div id="app">
      {{ message }}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

main.js

var app = new Vue({
  el: '#app',
  data: {
    message: "Hello Vue.js!"
  },
})

htmlファイルを開き、"Hello Vue.js!"と表示されればひとまずOK

APIからデータを取得する

mounted:...でページが読み込まれた時の記述を追加する

main.js

var app = new Vue({
  el: '#app',
  data: {
    message: "Hello Vue.js!"
  },
+ mounted: function(){
+   axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${"Tokyo"},jp&units=metric&appid=APIキー`)
+   .then(function(response){
+     console.log(response)
+   })
+   .catch(function(error){
+     console.log(error)
+   })
+ },
})

axios.getのところでAPIを叩く
q=Tokyo,q=Osakaのように書くことで気象情報を取り出す場所を指定することができる。
今回は後から変数を用いるので変数展開で書く。

"appid="の後にAPIキーを記述すること。

htmlファイルを開きconsole画面を見ると現在の気象情報データをとってこれているはず。

スクリーンショット 2019-11-27 19.34.49.png

data/name:場所
data/weather[0]/main:現在の天気
が格納されているのでこれらを画面に表示するようにする

天気を表示させる

main.js

var app = new Vue({
  el: '#app',
  data: {
+   city: null, 
+   condition:null,
  },
  mounted: function(){
    axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${"Tokyo"},jp&units=metric&appid=APIキー`)
    .then(function(response){
+     this.city = response.data.name
+     this.condition = response.data.weather[0].main
+   }.bind(this))
    .catch(function(error){
      console.log(error)
    })
  },
})

index.html

<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>WeatherApp</title>
  </head>
  <body>
    <div id="app">
+     <div class="weather_info">
+       <p class="weather_city">{{ city }}</p>
+       <p class="weather_condition">{{ condition }}</p>
+     </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

これでindex.htmlを開くと場所名(Tokyo)と現在の天気が表示されている筈。

さらにここから現在の気温を表示させてみる。
(気温は先ほどのconsole画面の中のdata/main/tempに格納されている)

main.js

var app = new Vue({
  el: '#app',
  data: {
    city: null, 
+   temp: null, 
    condition:null,
  },
  mounted: function(){
    axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${"Tokyo"},jp&units=metric&appid=APIキー`)
    .then(function(response){
      this.city = response.data.name
      this.condition = response.data.weather[0].main
+     this.temp = response.data.main.temp
    }.bind(this))
    .catch(function(error){
      console.log(error)
    })
  },
+ filters: {
+   roundUp(value){
+     return Math.ceil(value)
+   }
+ }
})



index.html

<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>WeatherApp</title>
  </head>
  <body>
    <div id="app">
      <div class="weather_info">
        <p class="weather_city">{{ city }}</p>
        <p class="weather_condition">{{ condition }}</p>
+       <p class="weather_temp">{{ temp | roundUp }}&deg;C</p>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

気温は少数も含まれているので
今回はfiltersオプションを用いて小数点切り上げして出力するようにしている。
これでindex.htmlを開くと以下のように表示される

スクリーンショット 2019-11-27 20.06.24.png

気象データの場所を選択できるようにする

今の状態だとTokyoの気象情報しか持ってこれないので
場所を選択できるようにしてみる。

main.js

var app = new Vue({
  el: '#app',
  data: {
    city: null, 
    temp: null, 
    condition:null,
+   selectedCity: '',
  },
+ methods:{
+   select: function() {
      axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${this.selectedCity},jp&units=metric&appid=APIキー`)
      .then(function(response){
        this.city = response.data.name
        this.temp = response.data.main.temp
        this.condition = response.data.weather[0].main
      }.bind(this))
      .catch(function(error){
        console.log(error)
      })
    }
  },
  filters: {
    roundUp(value){
      return Math.ceil(value)
    }
  }
})

index.html

<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>WeatherApp</title>
  </head>
  <body>
    <div id="app">
      <div class="weather_info">
        <p class="weather_city">{{ city }}</p>
        <p class="weather_condition">{{ condition }}</p>
        <p class="weather_temp">{{ temp | roundUp }}&deg;C</p>
      </div>
+     <select v-model="selectedCity" v-on:change="select">
+       <option disabled value="">Please select one</option>
+       <option>Tokyo</option>
+       <option>Osaka</option>
+       <option>Yokohama</option>
+       <option>Sapporo</option>
+     </select>
+     <span>Selected: {{ selectedCity }}</span>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

v-model="selectedCity"
でselectedCityの値を双方向データバインディングさせる。
axios.get...の中の変数展開でselectedCityを指定しているので、このselectedCityの値が気象情報を取得する場所となる。
v-on:change="select"と書かれている通り、選択ボックスをクリックするとAPIを叩くようになっている。

index.htmlを開くとこのように動く筈。

test.gif

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

v-ifで同一のコンポーネントの表示を切り替えた時にcreatedが発火しない問題とその解決

たぶんすごい基礎中の基礎なんだろうけど、結構詰まったので。

どういうことか

例えばこんなコンポーネントがあるとする。
渡したvalueを表示するだけのコンポーネント

TestComponent.vue
<template>
  <div>
    {{ value }}
  </div>
</template>

<script>
export default {
  props: {
    value: { type: String }
  },
  created() {
    console.log("created");
  }
};
</script>

こんな感じで利用する

Main.vue
<template>
  <div>
    <button @click="click">Click</button>
    <div>
      <testComponent value="a" v-if="testOpen"/>
      <testComponent value="b" v-else/>
    </div>
  </div>
</template>

<script>
import testCompoment from "TestComponent"

export default {
  data() {
    return {
      testOpen: false
    }
  },
  components: {
    testComponent
  },
  methods: {
    click() {
      this.testOpen = !this.testOpen;
    }
  }
};
</script>

クリックすると片方のコンポーネントが表示され、もう片方が消え、画面表示のa,bが切り替わる画面

予測していた動作

クリックをするたびにcreatedが呼ばれ、画面表示のa,bが切り替わる。

実際の動作

createdが最初しか呼ばれない。

See the Pen wvvVJXj by 7tsuno (@7tsuno) on CodePen.

なぜか

Vue は要素を可能な限り効率的に描画しようとする。
ここでは二つのtestComponent要素が同じものだとされ、初期化処理が走らないようになっている。

解決策

コンポーネントに別々のkeyをつける。
keyをつけることでvueに"この2つの要素は完全に別個のもので、再利用しないでください"と伝えることが出来る。

Main.vue
<template>
  <div>
    <button @click="click">Click</button>
    <div>
      <testComponent value="a" v-if="testOpen" key="testA"/>
      <testComponent value="b" v-else key="testB"/>
    </div>
  </div>
</template>

<script>
import testCompoment from "TestComponent"

export default {
  data() {
    return {
      testOpen: false
    }
  },
  components: {
    testComponent
  },
  methods: {
    click() {
      this.testOpen = !this.testOpen;
    }
  }
};
</script>

参考

公式 : 条件付きレンダリング

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

Vue.jsについて学んだことまとめ

目次

  1. Vue.jsとは
  2. Vue.jsの基本

今回の学習のゴール

  • Vue.jsとは何か、何ができるかを知る
  • 実際の基本的な使い方について知る

1. Vue.jsとは

Vue (発音は / v j u ː / 、 view と同様)はユーザーインターフェイスを構築するためのプログレッシブフレームワークです。他の一枚板(モノリシック: monolithic)なフレームワークとは異なり、Vue は少しずつ適用していけるように設計されています。
Vue.js

  • それぞれの意味

    • ユーザーインターフェイス(UI): コンピュータとユーザーがそれぞれ情報をやり取りする際の入力や表示方法などの仕組み
      • ex. GUI, CUI
      • Vue.jsはUIを構造化し、UIを個別にコンポーネント化できるため、システム全体をコンポーネントの集合として開発できる
    • プログレッシブフレームワーク : Vue.js作者のEvan You氏が提唱する概念
      • サポートするツールやライブラリによって、どんな規模でもどの段階のアプリケーションでも柔軟に対応できる
  • 特徴

    • View層にフォーカスしたJavaScriptライブラリ
      • View層 : レスポンスをクライアントにとって都合のいい画面に変換する層(実際のユーザーの目に見える領域)
    • 再利用可能な部品(コンポーネント)として様々な機能を実装できる
    • HTMLをベースとするテンプレート構文を利用することで様々な表示をHTMLの中に組み込むことができる
    • DOM要素とJavaScriptのデータを結びつけるリアクティブなデータバインディング
      • リアクティブなデータバインディングとは、HTMLテンプレート内で対象となるDOM要素にバインディングを指定することで、Vue.jsがそのデータの変更を検出する度に、バインディングされているDOM要素が自動で表示内容を更新すること
        • DOM : Document Object Modelの略称で、プログラムからHTMLを自由に操作するための仕組み
        • リアクティブ : 再度アクティブになるという意味
          • データが更新されると自動的にそれらのデータを利用している画面の表示も自動的に更新されるので、データを操作するだけで表示を更新できる
        • バインディング : 複数の事柄どうしを結びつけたり対応づけたりすること
      • Vue.jsは基本的にJavaScriptのスクリプトを使って、HTMLのタグを操作するものと考えて良い

2. Vue.jsの基本

Vueの導入

  • HTMLファイルの<head>タグ内に以下を記述
html
// 開発バージョン
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

// 本番バージョン
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

プロジェクトとしての開発

  • プロジェクトの作成
    • プログラムの作成に必要なスクリプトファイルやイメージなどのファイル、各種のライブラリやフレームワークなどをまとめて管理するためのもの
$ vue create vue-app    # vue-appというプロジェクトを作成
  • プロジェクトを実行
$ npm run serve    # localhostに「Welcome to Your Vue.js APP」というデフォルトのページが表示される
  • プロジェクトの実行によって生成されたvue-appフォルダの中身はざっと以下の通り

    • フォルダ
      • .git : Gitというツールで使用するフォルダ
      • node_modules : npmで管理されるモジュール類がまとめられたフォルダ
      • public : HTMLやCSSなど公開されるファイル類が保管されたフォルダ
      • src : Vue.jsで作成したファイルなどがまとめられたフォルダ
    • ファイル
      • .eslintrc.js : ESLintで使うファイル
      • .gitignore : Gitというツールで使用するファイル
      • package.json : npmでパッケージ管理するための設定ファイル
      • postcss.config.js : PostCSSというツールの設定ファイル
      • READM .md
  • 作成したプロジェクトをWebサーバーにアップして公開する

$ npm run build    # distというフォルダが作成される
  • ビルドによって生成されたdistフォルダの中身はざっと以下の通り
    • フォルダ
      • CSS
      • js
      • img
    • ファイル
      • favicon.ico
      • index.html : デフォルトで表示されるHTMLファイル

宣言的レンダリング

  • 単純なテンプレートにレンダリングする対象を宣言的に記載することでデータを変更するたびに、リアクティブなDOMレンダリングと、ユーザによる入力データの同期が可能である
    • レンダリング : 画面に表示すべき内容を、与えられたデータから計算しながら描き出すこと
    • 宣言的(Declarative) : 何がしたいのかという目的を宣言すること(処理の仕方はコンピュータに任せる)
      • 反対語である命令的(Imperative)は、どのようにしたいのかという方法を細かく指示すること

→ 要は、画面に表示したい内容を以下のようにテンプレートに当てはめるだけで、簡単に描画することができるってことが言いたいのだろう

html
<div id="app">    <!-- IDを使って操作の対象となるタグを指定する -->
  {{ message }}    <!-- mustache構文 HTMLの中に値を埋め込むためのもの -->
</div>
js
var app = new Vue({    // Vueというオブジェクトを作成し変数appに代入
  // オプションを記載 使用するデータやメソッドを定義する
  el: '#app',    // アプリケーションを紐付ける要素(element) DOM要素を示すセレクタを指定する
  data: {    // アプリケーションで使用するデータ dataに用意された値が対象となるタグ内の{{}}に置き換えられる
    message: 'Hello Vue!'    // この値に応じて描画が更新される
  }
})
表示内容
Hello Vue!

renderプロパティを用いたレンダリング

  • renderは、Vueに用意されているレンダリングを行うためのプロパティ
    • 上記の例はHTMLに用意された{{}}に値をはめ込んでいるだけだが、renderを使用すると表示内容そのものを作成することができる
    • 以下の例では、renderが<p>・・・・・data.message・・・・・</p>のようなタグそのものを作成している
html
<body>
  <h1>Vue.js</h1>
  <div id="app">
    {{ message }}
  </div>
  <hr>
  <button onclick="doAction();">    <!-- onclick属性でbutton要素がクリックされたかをイベントとして判断できる doAction()関数でアクションを実行-->
    click
  </button>
</body>
js
var data = {
  message: 'Hello Vue!'
};

var app = new vue ({
  el: '#app',
  data: data,    // dataプロパティの値には変数dataを指定
  render: (element) => {    // renderプロパティで表示内容を作成 elで設定したタグの部分にレンダリングしたものをはめ込むことができる アロー関数の引数elementには関数が渡されている
    return element("p",    // returnで関数elementの実行結果を返す 引数には追加するタグ名とその属性、追加するタグに表示する値を指定
      {'style':'font-size:20pt; coler:red;'},    // 第2引数の属性は省略可
      data.message);
  }
})

function doAction() {                 // doAction()関数が呼び出されると
  data.message = "You Clicked!!!";    // 変数dataのmessageが上書きされる
}
ページをリロードしたときの表示内容
Hello Vue!

→ renderで組み込んだpタグの内容(=変数dataに用意したmessageのテキスト)が、font-sizeは20pt、colerはredで表示される

clickボタンを押下したときの表示内容
You Clicked!!!

→ リアクティブ機能が働いていることがわかる

※ returnの戻り値はVNodeというオブジェクト
  • VNodeは関数returnで生成されるもので、仮想DOMとして扱われるオブジェクトのこと
    • DOMとはData Object Modelの略で、HTMLなどの1つ1つの要素(タグ)をプログラミング言語で扱えるようにするために用意された仕組み
  • JavaScriptではHTMLを読み込んだ段階で、各ダグの要素が作成され、それらの組み込み状態などがHTMLと全く同じ形で再現される(組み込まれて構造的に作られた構造をDOMツリーという)
  • Vue.jsではDOMツリーが構築された後、それぞれの要素に対する仮想DOMの要素が作られ、仮想DOMのDOMツリーが作成される
  • 仮想DOMの値が変更されれば、常にDOMの値も更新されるような対応がなされており、HTMLの表示タグも変更される
  • DOMツリーは状態を常にチェックできないが(状態を常にチェックしたい場合はその処理を記述する必要がある)、仮想DOMは常に状態が管理され、変更があるたびに実際のDOMに変更内容が反映される
  • 表示内容を複数のタグを組み合わせて表示させたい場合は、次のように子ノードを組み込む return 関数 ( タグ名, { 属性 }, [ VNode, VNode, ・・・・・] )
var app = new vue ({
  el: '#app',
  data: data,
  render: (element) => {
    return element("ol",    // <li>タグを<ol>タグの子ノードとして組み込む
    // element関数を使って<li>タグのVNodeのオブジェクトを作成 配列に値をまとめる
    [
      element("li", "item 1"),
      element("li", "item 2"),
      element("li", "item 3")
    ]);
  }
})
表示内容
1. item 1
2. item 2
3. item 3

要素の属性をバインディング

  • タグのコンテンツとしてテキストを表示させる場合は{{}}記号を使用したが、タグの属性の値に変数などを設定したい場合はv-bind:を使う
html
<h1>Vue.js</h1>
<div id="app">
  <p v-bind:style="style">{{ message }}</p>    <!-- v-bind:style属性にstyle変数の中身が設定される -->
</div>
<hr>
<input type="number" onChange="doChange(event);" value="20">    <!-- onChange属性の値に入力フォームの内容が変更された場合の処理を記述 -->
js
var data = {
  message: 'Hello Vue!'
  style: 'font-size:20pt; color:red;'
};

var app = new vue ({
  el: '#app',
  data: data
});

function doCahnge(event) {
  data.style = 'font-size:' + event.target.value + 'pt; color:red;';    // event.target.valueでイベントが発生した対象(入力フォーム)の値を取り出す style属性の値が変更される
}

→ inputタグに入力されたnumberに合わせてfont-sizeが変更する

条件分岐とループ

  • v-if ディレクティブは、条件に応じて描画したい場合に使用され、ディレクティブの式が真を返す場合のみ描画される
html
<div id="app">
  <span v-if="seen">Now you see me</span>    <!-- 値には真偽値として扱える変数などの条件を記述 trueであれば表示内容が表示される-->
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    seen: true
  }
})
表示内容
Now you see me
  • v-forディレクティブは、リストの項目を配列内のデータを使って表示させることができる
html
<div id="app">
  <ol>
    <li v-for="todo in todos">    <!-- "変数 in 配列"と記述し、配列の値を順に取り出し変数に入れてダグを出力する -->
      {{ todo.text }}
    </li>
  </ol>
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    todos: [
      { text: 'Learn JavaScript' },
      { text: 'Learn Vue' },
      { text: 'Build something awesome' }
    ]
  }
})
表示内容
1. Learn JavaScript
2. Learn Vue
3. Build something awesome

ユーザー入力の制御

  • v-onディレクティブを使ってイベントリスナを加え、Vueインスタンスのメソッドを呼び出す
    • イベントリスナ : イベントに対して処理を結びつける仕組み
html
<div id="app">
  <p>{{ message }}</p>
  <button v-on:click="reverseMessage">Reverse Message</button>    <!-- v-on:クリック名 = "処理(今回は関数の名前)"と記述する -->
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js!'
  },
  methods: {    // v-onの値に記述した関数の名前はmethodsプロパティに用意する
    reverseMessage: function () { 
      this.message = this.message.split('').reverse().join('')
    }
  }
})
ボタンを押した時の表示内容
!sj.euV olleH
  • v-modelディレクティブを使って入力とアプリケーションの状態の「双方向バインディング」を行う
    • inputタグに入力された値をVueのdataプロパティの値にバインドする機能である
    • リアルタイムにバインドされた変数の値が更新されるので、下記の例ではpタグで表示される内容とinputタグに入力されている内容がリアルタイムで同等の値になる
html
<div id="app">
  <p>{{ message }}</p>
  <input v-model="message">    <!-- 変数messageにバインド -->
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

コンポーネントシステム

  • 自己完結的で、再利用可能である小さい単位のコンポーネントを組み合わせることによって、大規模のアプリケーションを構築できる
    • 以下の例は変数をコンポーネントに渡す処理
html
<h1>Vue.js</h1>
<div id="app">
  <hello/>    <!-- helloコンポーネントを使用 -->
</div>
js
Vue.component('hello',    // helloコンポーネントを定義
{
  data:function() {    // コンポーネントのdataプロパティに変数の値を関数として用意する必要がある
    return {
      message: 'New message'    // 変数の情報
    };
  },
  template: '<p class="hello">{{message}}</p>',    // templateに設定した内容がコンポーネントとして表示される dataで設定したmessageの値が埋め込まれている
});

var app = new Vue({    // コンポーネント用のタグをレンダリングするためにVueオブジェクトの処理も行う
  el: '#app',
});

参照

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

超初心者でも出来るVueを使ったカレンダー作成

はじめに

フツーの会社員として働きながら、プログラミンを学び始めて2か月が経過しました。少しづつコード
は勉強していますが、まだまだやってみたことと実力の差が正直大きいです。そんな時、WEBフロントエンド用のフレームワークであるVue.jsを知ったので、試してみることにしました。

Vueとは?(超初心者の備忘録的に記載してます)

フロントエンド(front end)とはWebサービスやWebアプリケーションなどでユーザーがボタンを押したり入力をしたりする部分、またソフトウェアなどと直接やり取りをする部分のことを指します。
ユーザーがWebサービスなどを利用する時に目に見える表の部分はフロントエンドの部分です。(参考)
Vue.jsはフロントエンド開発支援のためのツールで、「こんな感じのパーツが欲しいなあ」と思うものに対し簡単に作れるように準備されています。ライブラリをインストールしてコードをコピーし、自分がカスタマイズしたい部分だけ変更すれば、あら不思議、作りたいものが簡単にできちゃいます(部分的だけど)。
今までコツコツ手計算していた難しい計算問題を一気にエクセルが解決してくれるくらいの有難さですね。
もうちょっとちゃんとした説明は下記のサイトにあります。
https://techacademy.jp/magazine/21456
https://jp.vuejs.org/v2/guide/

仕様

年末ということもあり、今の実力では作れないカレンダーをに挑戦しました。
・2か月分表示される
・カレンダーから日付をピックすることもできる。

環境

Node.js v10.16.3
Windows 10 pro
Visual Studio Code v1.39.1
Vue.js

方法

「Vue.jsのカレンダライブラリv-calendarを使って、日付ごとのデータをカレンダー上に表示」も参考にさせていただきました。
もっとスマートなやり方があるとは思いますが、初心者が再現しやすいように書いておきます。

1.ターミナルを起動し、npm init -yでpackage.jasonをインストール
2.次に同じくターミナルでnpm install v-calenderでVue.jsのカレンダーライブラリーをインストール
3. index.jsファイルを作成(下記index.jsコード参照)
4. main.jsファイルを作成(下記main.jsコードを参照)
5. publicフォルダを作成し、中にindex.html作成
6. heroku化のために.gitignore というファイル名でindex.jsなどと同じ場所に保存します(コードは一番下のコードを参照)。
7. Heroku化してサーバーにアップする。

コード

index.js
var express = require('express');
var app = express();

// public というフォルダに入れられた静的ファイルはそのまま表示
app.use(express.static(__dirname + '/public'));

// bodyParser
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.post('/post', function(req, res) {
  for (key in req.body) {
    console.log(key, '=', req.body[key]);
  }
  res.end();
});
// app.listen(8080);
app.listen(process.env.PORT || 8008);

console.log("server start! (heroku)");
main.js
import Vue from 'vue'
import App from './App.vue'

import VCalendar from 'v-calendar'

Vue.use(VCalendar) 
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')   
```index.html
<html>
    <style>

        </style>

    <head>
           //ブラウザーのタグの所の名前
        <title>Hello Server!</title>
        <script src="https://unpkg.com/vue"></script>
        <meta charset='utf-8'>
        <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
        <meta http-equiv='x-ua-compatible' content='ie=edge'>
        <!-- IMPORTANT: No CSS link needed as of v1 Beta (@next) - It's all inlined -->
        <!-- Pre v1.0.0 versions need the minified css -->
        <!-- <link rel='stylesheet' href='https://unpkg.com/v-calendar/lib/v-calendar.min.css'> -->
      </head>
    </head>

    <body>
         //カレンダーの上の文言
        <div>Calender</div>
        <div id='app'>
            <v-calendar :columns="$screens({ default: 1, lg: 2 })" /></v-calendar>
            <v-date-picker :mode='mode' v-model='selectedDate' />
          </div>

          <!-- 1. Link Vue Javascript -->
          <script src='https://unpkg.com/vue/dist/vue.js'></script>

          <!-- 2. Link VCalendar Javascript (Plugin automatically installed) -->
          <!-- @next v1 Beta  -->
          <script src='https://unpkg.com/v-calendar@next'></script>
          <!-- Latest stable (Right now, this is very different from the v1 Beta)-->
          <!-- <script src='https://unpkg.com/v-calendar'></script> -->
          <!-- Hardcoded version -->
          <!-- <script src='https://unpkg.com/v-calendar@1.0.0-beta.14/lib/v-calendar.umd.min.js'></script> -->
          <!--3. Create the Vue instance-->
          <script>
            new Vue({
              el: '#app',
              data: {
                // Data used by the date picker。datePickerは複数選択できる
                mode: 'multiple',
                selectedDate: null,
              }
            })
          </script>
          <template>
             <vc-calendar :attributes='attributes5' style="width:500px;">
               <template slot='header-title' slot-scope='page'>
                 {{page.yearLabel}}年{{page.monthLabel}}
               </template>
               <template slot='day-content' slot-scope='props'>
                 <div class="vc-day-content">
                 <div v-bind:style="addStyleTextColor(props.day.weekday)">
                   {{ props.day.day }}</div>
                 </div>
                 <div v-if="props.day.day % 2 ==0" style="text-align:center;">
                   <img src="../assets/gatsby.svg">
                 </div>
                 <div v-else style="text-align:center">
                   <img src="../assets/slack.svg">
                 </div>
               </template>
             </vc-calendar>
           </template>
    </body>
</html>
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# next.js build output
.next

出力結果

2か月分のカレンダーを表示し、選択した日付が表示されています。
image.png

感想

自分ではどうやってコードを書いてカレンダーを作成できるのかわからない・・・。だけど「出来ちゃうんだあ~」。勉強不足を棚に上げてうれしいですね。無料でこんなツールを提供いただけるなんて、プログラミング業界はなんて心が広いのでしょうか。
ただ今回は、ほぼカレンダーが表示されているだけなので、今後はもう少しレベルアップして予定をインプットできるようにもしていきたいです。

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