20200715のvue.jsに関する記事は18件です。

Vue.js + Firebase Googleアカウントでログイン

前提

MacOS
VueCLI導入済み
Firebase導入・認証設定済み

本題

ログイン

src/store.js
//省略
//firebaseをインポート
import firebase from "firebase";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    //追記
    login_user: null,
  },
  mutations: {
    //追記
    setLoginUser(state, user) {
      state.login_user = user;
    },
    addAddress(state, { id, address }) {
      address.id = id;
      state.addresses.push(address);
    },
    updateAddress(state, { id, address }) {
      const index = state.addresses.findIndex((address) => address.id === id);

      state.addresses[index] = address;
    },
    deleteAddress(state, { id }) {
      const index = state.addresses.findIndex((address) => address.id === id);

      state.addresses.splice(index, 1);
    },
  },
  actions: {
    //追記
    setLoginUser({ commit }, user) {
      commit("setLoginUser", user);
    },
    login() {
      const google_auth_provider = new firebase.auth.GoogleAuthProvider();
      firebase.auth().signInWithRedirect(google_auth_provider);
    },
    toggleSideMenu({ commit }) {
      commit("toggleSideMenu");
    },
    addAddress({ getters, commit }, address) {
      if (getters.uid) {
        firebase
          .firestore()
          .collection(`users/${getters.uid}/addresses`)
          .add(address)
          .then((doc) => {
            commit("addAddress", { id: doc.id, address });
          });
      }
    },
    updateAddress({ getters, commit }, { id, address }) {
      if (getters.uid) {
        firebase
          .firestore()
          .collection(`users/${getters.uid}/addresses`)
          .doc(id)
          .update(address)
          .then(() => {
            commit("updateAddress", { id, address });
          });
      }
    },
    deleteAddress({ getters, commit }, { id }) {
      if (getters.uid) {
        firebase
          .firestore()
          .collection(`users/${getters.uid}/addresses`)
          .doc(id)
          .delete()
          .then(() => {
            commit("deleteAddress", { id });
          });
      }
    },
  },
  modules: {},
  getters: {
    userName: (state) => (state.login_user ? state.login_user.displayName : ""),
    photoURL: (state) => (state.login_user ? state.login_user.photoURL : ""),
    uid: (state) => (state.login_user ? state.login_user.uid : null),
    getAddressById: (state) => (id) =>
      state.addresses.find((address) => address.id === id),
  },
});

続いて、ログインアクションを実行できるようにする。

views/home.vue
<template>
  <v-container text-center justify-center>
    <v-layout row wrap>
      <v-flex xs12>
        <h1>TEST</h1>
        <p>
          Googleアカウントでログイン
        </p>
      </v-flex>

      <v-flex xs12 mt-5>
        <v-btn color="info" @click="login">Googleアカウントでログイン</v-btn>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
//mapActionsを使用してmethodsに組み込む
import { mapActions } from "vuex";

export default {
  methods: {
    ...mapActions(["login"]),
  },
};
</script>
src/App.vue
<template>
  <v-app>
    <v-app-bar app color="primary" dark>
      <v-app-bar-nav-icon
        v-show="$store.state.login_user"
        @click.stop="toggleSideMenu"
      ></v-app-bar-nav-icon>
      <v-toolbar-title>TEST</v-toolbar-title>
      <v-spacer></v-spacer>
    </v-app-bar>
    <SideNav />

    <v-content>
      <v-container fluid fill-height align-start>
        <router-view />
      </v-container>
    </v-content>
  </v-app>
</template>

<script>
//追記
import firebase from "firebase";
import { mapActions } from "vuex";
//省略

export default {
  name: "App",

  components: {
    SideNav,
  },
  //追記
  created() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        this.setLoginUser(user);
        this.fetchAddresses();
        if (this.$router.currentRoute.name === "home") {
          this.$router.push({ name: "addresses" }, () => {});
        }
      } else {
        this.deleteLoginUser();
        this.$router.push({ name: "home" }, () => {});
      }
    });
  },
  methods: {
    ...mapActions([
      "toggleSideMenu",
      "setLoginUser",
    ]),
  },
};
</script>

ログアウト

src/store.js
//省略

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    login_user: null,
  },
  mutations: {
    setLoginUser(state, user) {
      state.login_user = user;
    },
    //追記
    deleteLoginUser(state) {
      state.login_user = null;
    },
    addAddress(state, { id, address }) {
      address.id = id;
      state.addresses.push(address);
    },
    updateAddress(state, { id, address }) {
      const index = state.addresses.findIndex((address) => address.id === id);

      state.addresses[index] = address;
    },
    deleteAddress(state, { id }) {
      const index = state.addresses.findIndex((address) => address.id === id);

      state.addresses.splice(index, 1);
    },
  },
  actions: {
    setLoginUser({ commit }, user) {
      commit("setLoginUser", user);
    },
    login() {
      const google_auth_provider = new firebase.auth.GoogleAuthProvider();
      firebase.auth().signInWithRedirect(google_auth_provider);
    },
    //追記
    logout() {
      firebase.auth().signOut();
    },
    //追記
    deleteLoginUser({ commit }) {
      commit("deleteLoginUser");
    },
    toggleSideMenu({ commit }) {
      commit("toggleSideMenu");
    },
    addAddress({ getters, commit }, address) {
      if (getters.uid) {
        firebase
          .firestore()
          .collection(`users/${getters.uid}/addresses`)
          .add(address)
          .then((doc) => {
            commit("addAddress", { id: doc.id, address });
          });
      }
    },
    updateAddress({ getters, commit }, { id, address }) {
      if (getters.uid) {
        firebase
          .firestore()
          .collection(`users/${getters.uid}/addresses`)
          .doc(id)
          .update(address)
          .then(() => {
            commit("updateAddress", { id, address });
          });
      }
    },
    deleteAddress({ getters, commit }, { id }) {
      if (getters.uid) {
        firebase
          .firestore()
          .collection(`users/${getters.uid}/addresses`)
          .doc(id)
          .delete()
          .then(() => {
            commit("deleteAddress", { id });
          });
      }
    },
  },
  modules: {},
  getters: {
    userName: (state) => (state.login_user ? state.login_user.displayName : ""),
    photoURL: (state) => (state.login_user ? state.login_user.photoURL : ""),
    uid: (state) => (state.login_user ? state.login_user.uid : null),
    getAddressById: (state) => (id) =>
      state.addresses.find((address) => address.id === id),
  },
});
src/App.vue
<template>
  <v-app>
    <v-app-bar app color="primary" dark>
      <v-app-bar-nav-icon
        v-show="$store.state.login_user"
        @click.stop="toggleSideMenu"
      ></v-app-bar-nav-icon>
      <v-toolbar-title>TEST</v-toolbar-title>
      <v-spacer></v-spacer>
      //追記
      <v-toolbar-items v-if="$store.state.login_user">
        <v-btn text @click="logout">ログアウト</v-btn>
      </v-toolbar-items>
    </v-app-bar>
    <SideNav />

    <v-content>
      <v-container fluid fill-height align-start>
        <router-view />
      </v-container>
    </v-content>
  </v-app>
</template>

<script>
//追記
import firebase from "firebase";
import { mapActions } from "vuex";
//省略

export default {
  name: "App",

  components: {
    SideNav,
  },
  //追記
  created() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        this.setLoginUser(user);
        this.fetchAddresses();
        if (this.$router.currentRoute.name === "home") {
          this.$router.push({ name: "addresses" }, () => {});
        }
      } else {
        this.deleteLoginUser();
        this.$router.push({ name: "home" }, () => {});
      }
    });
  },
  methods: {
    ...mapActions([
      "toggleSideMenu",
      "setLoginUser",
      //追記
      "logout",
      "deleteLoginUser",
      "fetchAddresses",
    ]),
  },
};
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】複数のインスタンスを作成する方法

page2というインスタンスを作成しようと思います。

vueファイル内でnew Vueと書くことはできないので、main.jsnew Vueを書くことによって複数のインスタンスを作成します。

export defaultと書くことによってvueインスタンスを生成します(なんか足りてない、または間違っている気がするので見直してみます。)

main.js

・main.jsにnew Vueを書くことによって複数のインスタンスを作成します。
・作成するpage2をインポートする必要があります。

main.js
import Vue from 'vue'
import App from './App.vue'
import page2 from './components/page2.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

new Vue({
  router,
  render: h => h(page2),
}).$mount('#page2')

page2.vue

・id はpage2とする必要があります。
・script内のexport defaultで下記を記述する必要があります。

name: 'page2',
  components: {
  }
}
page2.vue
<template>
  <div id="page2">
<router-view></router-view>


ああああ
  </div>

</template>


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

これで、App.vueとpage2.vueという2つのインスタンスをアプリ内に作成することができました。

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

【Vue.js】複数のインスタンスを作成する方法【初心者】

現在Vue.jsを勉強しています。
Appというインスタンスの他にpage2というインスタンスを作成しようと思います。

vueファイル内でnew Vueと書くことはできないので、main.jsnew Vueを書くことによって複数のインスタンスを作成します。

コンポーネントを他のコンポーネントでも使えるようにするには、script内をexport defaultで囲むことによってvueインスタンスを生成します。したがって、基本的にscriptの部分は export default で囲むことが前提となります。(なんか足りてない、または間違っている気がするので見直してみます。)

参考:export defaultについて

main.js

・main.jsにnew Vueを書くことによって複数のインスタンスを作成します。
・作成するpage2をインポートする必要があります。

main.js
import Vue from 'vue'
import App from './App.vue'
import page2 from './components/page2.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

new Vue({
  router,
  render: h => h(page2),
}).$mount('#page2')

page2.vue

・id はpage2とする必要があります。
・script内のexport defaultで下記を記述する必要があります。

name: 'page2',
  components: {
  }
}
page2.vue
<template>
  <div id="page2">
<router-view></router-view>


ああああ
  </div>

</template>


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

これで、元々あったAppに加え、page2という2つのインスタンスをアプリ内に作成することができました。

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

Nuxt.js(Vue) で自作モーダル作成する方法【ライブラリ使用無し】

Nuxt.js(Vue) でライブラリ等を使用せずモーダルを作成する方法です。

modal1gif.gif

モーダル用のコンポーネントを作る

Modal.vue
<template>
  <transition name="modal" appear>
    <div class="modal__overlay">
      <div class="modal__window">
        <div class="modal__content">
          <slot />
        </div>
      </div>
    </div>
  </transition>
</template>

<style lang="scss" scoped>
.modal {
  &__overlay {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    z-index: 100;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    background: rgba(0, 0, 0, 0.7);
  }

  &__window {
    height: 70%;
    width: 70%;
    overflow: hidden;
    background-color: aquamarine;
  }

  &__content {
    height: 100%;
    padding: 30px;
  }
}

// transition
.modal-enter-active,
.modal-leave-active {
  transition: opacity 0.4s;
  .modal__window {
    transition: opacity 0.4s, transform 0.4s;
  }
}
.modal-leave-active {
  transition: opacity 0.6s ease 0.4s;
}
.modal-enter,
.modal-leave-to {
  opacity: 0;
  .modal__window {
    opacity: 0;
    transform: translateY(-20px);
  }
}
</style>

<slot/>を使うことでモーダル中身を変更できるようになります。一度このコンポーネントを作ってしまえば使い回しも容易です。

上記で作ったmodalコンポーネントをモーダルを実装したいコンポーネントで読み込みます。

index.vue
<template>
  <div >
    <button class="help_link__button" @click="openModal">
      モーダルを開く
    </button>
    <Modal v-if="modalFlag">
      <div>モーダルの内容</div>
      <div>モーダルの内容</div>
      <div>モーダルの内容</div>
      <div>モーダルの内容</div>
      <div>モーダルの内容</div>
      <button @click="closeModal">閉じる</button>
    </Modal>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import Modal from '~/components/Modal.vue'

export default Vue.extend({
  components: {
    Modal
  },
  data() {
    return {
      modalFlag: false
    }
  },
  methods: {
    openModal() {
      this.modalFlag = true
    },
    closeModal() {
      this.modalFlag = false
    }
  }
})
</script>

@click="openModal"modalFlag = true or falseに切り替えています。

modal1gif.gif

上記の画像のように、閉じるボタンを押せばモーダルが消える仕様です。

どこを押しても閉じるモーダルの実装

モーダルの実装方法で、下記の画像のように、外枠のどこを押しても閉じるパターンがあります。
そのパターンの実装方法です。
modal2.gif

Modal.vue
<template>
  <transition name="modal" appear>
    <div class="modal__overlay" @click="closeModal"> 
      <div class="modal__window">
        <div class="modal__content">
          <slot />
        </div>
      </div>
    </div>
  </transition>
</template>

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  methods: {
    closeModal() {
      this.$emit('close-modal')
    }
  }
})
</script>

<style lang="scss" scoped>
// 変更箇所ないので省略
</style>

外枠部分にclickイベントを設定します。そして、$emitを使い親要素のイベントを発火させます。

index.vue
<template>
  <div >
    <button class="help_link__button" @click="openModal">
      モーダルを開く
    </button>
    <Modal v-if="modalFlag" @close-modal="closeModal" >
      <div>モーダルの内容</div>
      <div>モーダルの内容</div>
      <div>モーダルの内容</div>
      <div>モーダルの内容</div>
      <div>モーダルの内容</div>
      <button @click="closeModal">閉じる</button>
    </Modal>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import Modal from '~/components/Modal.vue'

export default Vue.extend({
  components: {
    Modal
  },
  data() {
    return {
      modalFlag: false
    }
  },
  methods: {
    openModal() {
      this.modalFlag = true
    },
    closeModal() {
      this.modalFlag = false
    }
  }
})
</script>

@close-modal="closeModal"で子要素であるModal.vueのイベントを受け取っており、modalFlag = true or falseを切り替えています。
これで外枠どこを押しても閉じるモーダルウィンドウができます。
modal2.gif

最後に

今回は、ライブラリを使わずにNuxt.js(Vue) で、自作モーダル作成する方法をまとめました。
一度コンポーネントを作ってしまえば、使い回しもできるのでかなり便利です。

ライブラリを使ってサクッとモーダルを実装する方法も下記の記事でまとめているので、良かったらご覧ください。
【vue-js-modal】を使ってNuxt.js(vue.js)でモーダルを実装してみた

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

Vue.js v-on:clickについて

v-on:clickで時間表示ボタン作成

参考記事
https://www.youtube.com/watch?v=jdcZ3LvTs78&list=PLh6V6_7fbbo-SZYHHBVFstU2tp0dDZMAW&index=5

コード

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>Vueの練習</h1>
<div id="app">
    <p>{{now}}</p>
    <button v-on:click='time'>現在時刻を表示する</button>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue"></script>![スクリーンショット 2020-07-15 21.45.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670884/cf369723-391c-c2e9-1b15-454a9299b410.png)

<script>

let app = new Vue({
    el:'#app',
    data:{
        now: '00:00:00'
    },
    methods: {
        time: function(e){
            let date = new Date();
            this.now =date.getHours() + ':'
            + date.getMinutes() + ':' +
            date.getSeconds() + ':';
        }
    }
})


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

結果
スクリーンショット 2020-07-15 21.45.03.png

現在時刻を表示するボタンをおすと
スクリーンショット 2020-07-15 21.45.08.png

時刻が表示された。

v-on:click='time'

でボタンをクリックするとtime関数が実行される(関数はmethods内に記述する)。
実行の記述は以下

methods: {
        time: function(e){
            let date = new Date();
            this.now =date.getHours() + ':'
            + date.getMinutes() + ':' +
            date.getSeconds() + ':';
        }
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js v-bind の使い方(属性の扱いにも触れてます)

v-bindの使い方についての備忘録

参考記事
https://www.youtube.com/watch?v=qzFJfekigY8&list=PLh6V6_7fbbo-SZYHHBVFstU2tp0dDZMAW&index=4

コード

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .abc{
            color:red;
        }
        .abc2{
            background: blue;
        }
    </style>
</head>
<body>
    <h1>Vueの練習</h1>
<div id="app">
  <!-- <p class='error'>エラーです</p> -->
  <!-- <p class='{{error_class}}''>エラーです</p> -->
  <!-- ↑このように属性の中にマスタッシュ記法は出来ない。マスタッシュはhtmlタグに書かなくては機能しない -->
<!-- classの中に書きたいときは以下のような記法がある ↓-->
<p v-bind:class='error_class'>エラーです</p>


</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>

let app = new Vue({
    el:'#app',
    data:{
        error_class:'abc abc2',
        img_src:'https://cdn.pixabay.com/photo/2020/07/06/13/21/porsche-5377019__480.jpg'
    }
});



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

ポイントは
属性にはマスタッシュ記法が使えない

<p class='{{error_class}}''>エラーです</p> 

↑これのこと。この書き方は❌(マスタッシュが属性の中にある)

マスタッシュが使えるのはhtmlタグである。

<div id="app">{{test+count}}</div>

↑こういうのはOK(マスタッシュがhtmlタグ内にある)
では属性(タグ内のclassやimgタグのsrcなどをVueでいじるにはどうすれば良いか、、、

そこで使用するのが「v-bind」
使い方は以下
html側

<div id="app">
<p v-bind:class='error_class'>エラーです</p>
<img v-bind:src="img_src">
</div>

js側

let app = new Vue({
    el:'#app',
    data:{
        error_class:'abc abc2',
        img_src:'https://cdn.pixabay.com/photo/2020/07/06/13/21/porsche-5377019__480.jpg'
    }
});

Vueの記法内のdataで変数を設定(error_classとimg_src)。
そこに属性に当てたい内容を記述する。
これでcssや画像のURLをVueで指定することが出来る。

結果は以下
スクリーンショット 2020-07-15 21.22.42.png

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

Vue.js v-bind の使い方

v-bindの使い方についての備忘録

参考記事
https://www.youtube.com/watch?v=qzFJfekigY8&list=PLh6V6_7fbbo-SZYHHBVFstU2tp0dDZMAW&index=4

コード

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .abc{
            color:red;
        }
        .abc2{
            background: blue;
        }
    </style>
</head>
<body>
    <h1>Vueの練習</h1>
<div id="app">
  <!-- <p class='error'>エラーです</p> -->
  <!-- <p class='{{error_class}}''>エラーです</p> -->
  <!-- ↑このように属性の中にマスタッシュ記法は出来ない。マスタッシュはhtmlタグに書かなくては機能しない -->
<!-- classの中に書きたいときは以下のような記法がある ↓-->
<p v-bind:class='error_class'>エラーです</p>


</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>

let app = new Vue({
    el:'#app',
    data:{
        error_class:'abc abc2',
        img_src:'https://cdn.pixabay.com/photo/2020/07/06/13/21/porsche-5377019__480.jpg'
    }
});



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

ポイントは
属性にはマスタッシュ記法が使えない

<p class='{{error_class}}''>エラーです</p> 

↑これのこと。この書き方は❌(マスタッシュが属性の中にある)

マスタッシュが使えるのはhtmlタグである。

<div id="app">{{test+count}}</div>

↑こういうのはOK(マスタッシュがhtmlタグ内にある)
では属性(タグ内のclassやimgタグのsrcなどをVueでいじるにはどうすれば良いか、、、

そこで使用するのが「v-bind」
使い方は以下
html側

<div id="app">
<p v-bind:class='error_class'>エラーです</p>
<img v-bind:src="img_src">
</div>

js側

let app = new Vue({
    el:'#app',
    data:{
        error_class:'abc abc2',
        img_src:'https://cdn.pixabay.com/photo/2020/07/06/13/21/porsche-5377019__480.jpg'
    }
});

Vueの記法内のdataで変数を設定(error_classとimg_src)。
そこに属性に当てたい内容を記述する。
これでcssや画像のURLをVueで指定することが出来る。

結果は以下
スクリーンショット 2020-07-15 21.22.42.png

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

【Vue.js】Elements in iteration expect to have 'v-bind:key' directivesの解決法

エラー

Elements in iteration expect to have 'v-bind:key' directives

原因

キー属性を指定していない。

解決法

各項目にキー属性を指定する必要があある。

エラーのコード

<li v-for="value in object">{{value}}</li>

解決するコード

<li v-for="value in object" :key='value'>{{value}}</li>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberry Pi で温湿度&気圧を計測し Firebase × Vue.js (&vue-chartjs) で可視化

はじめに

Firebase と Vue.js で簡単に Web アプリを公開できると聞き、かねてから興味があったので、勉強のために自分で何か作ってみようと画策。目的が勉強なので、あとで忘れてしまっても思い出せるよう事細かに記載します。同様のことで苦戦している人の助けになれば幸いです。

全体像

  1. ラズパイに温湿度気圧センサ (BME280) を接続。通信は I2C。Python スクリプトで I2C 通信し、温湿度&気圧を取得
  2. 取得した温湿度&気圧情報を Firebase の Firestore (DB) へ add
  3. Firestore 内のセンサデータを読み取りグラフ作成するページを、Vue.js および Vue-chart.js で作成
  4. そのページを Firebase hosting で公開 全体図.001.png

以降、各手順を詳細に説明する。

0. 使用したもの

  • Raspberry Pi Zero WH
  • Google Firebase
  • MacBook Pro
  • BME280 (温湿度気圧センサ)
    • 今回は Amazon で KeeYees のものを購入。3 個で 1,800 円。
  • ジャンパー線
    • ラズパイとセンサの接続用

1. ラズパイで温湿度&気圧を取得する

ラズパイセットアップ

OS インストールから SSH 接続までを以前、別の記事でまとめたので、下記を参照。
https://qiita.com/wakodai/items/87580d1471d8894169e2

センサの接続

電源、GND、それから I2C の SCL(クロック)、SDA(データ)を接続するだけ。

気温、気圧、湿度の読み取り

I2C 有効化

I2Cはシリアル通信のプロトコルの 1 つである。ラズパイに標準で備わっている。今回使用するセンサにも備わっている。のでこれでセンサとラズパイは通信し、ラズパイは温湿度気圧情報を取得する。

I2C(アイ・スクエアド・シー、アイ・アイ・シー)はフィリップス社で開発されたシリアルバスである。低速な周辺機器をマザーボードへ接続したり、組み込みシステム、携帯電話などで使われている。

ラズパイに SSH でログインし、

ラズパイ側で実施
$ sudo raspi-config

で設定画面を開き、画面に従い I2C 有効化

Python スクリプト

センサから値を取得する Python スクリプトを作成する。まずはじめに、Python で I2C バスを使うために smbus ライブラリをインストールする。smbus とはなんぞやだが、smbus もシリアル通信プロトコルの 1 つで I2C をベースにしているため、Python 用に用意されている smbus ライブラリが使える、ということらしい。

ラズパイ側で実施
$ sudo pip3 install smbus2

でインストール。

※ 後述する firestore へデータを飛ばす部分のコードも含まれています。

hoge.py
#coding: utf-8

from smbus2 import SMBus
import time
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
import datetime

# 後述する firestore へデータを add するための権限ファイル (json) を読み込む
# json ファイルのパスを絶対パスにしないと cron で動かない部分でハマったので注意
cred = credentials.Certificate("/home/pi/Python/******.json")
firebase_admin.initialize_app(cred)
db = firestore.client()

bus_number  = 1
i2c_address = 0x76

bus = SMBus(bus_number)

digT = []
digP = []
digH = []

t_fine = 0.0

def writeReg(reg_address, data):
    bus.write_byte_data(i2c_address,reg_address,data)

def get_calib_param():
    calib = []

    for i in range (0x88,0x88+24):
        calib.append(bus.read_byte_data(i2c_address,i))
    calib.append(bus.read_byte_data(i2c_address,0xA1))
    for i in range (0xE1,0xE1+7):
        calib.append(bus.read_byte_data(i2c_address,i))

    digT.append((calib[1] << 8) | calib[0])
    digT.append((calib[3] << 8) | calib[2])
    digT.append((calib[5] << 8) | calib[4])
    digP.append((calib[7] << 8) | calib[6])
    digP.append((calib[9] << 8) | calib[8])
    digP.append((calib[11]<< 8) | calib[10])
    digP.append((calib[13]<< 8) | calib[12])
    digP.append((calib[15]<< 8) | calib[14])
    digP.append((calib[17]<< 8) | calib[16])
    digP.append((calib[19]<< 8) | calib[18])
    digP.append((calib[21]<< 8) | calib[20])
    digP.append((calib[23]<< 8) | calib[22])
    digH.append( calib[24] )
    digH.append((calib[26]<< 8) | calib[25])
    digH.append( calib[27] )
    digH.append((calib[28]<< 4) | (0x0F & calib[29]))
    digH.append((calib[30]<< 4) | ((calib[29] >> 4) & 0x0F))
    digH.append( calib[31] )

    for i in range(1,2):
        if digT[i] & 0x8000:
            digT[i] = (-digT[i] ^ 0xFFFF) + 1

    for i in range(1,8):
        if digP[i] & 0x8000:
            digP[i] = (-digP[i] ^ 0xFFFF) + 1

    for i in range(0,6):
        if digH[i] & 0x8000:
            digH[i] = (-digH[i] ^ 0xFFFF) + 1

def readData():
    pressure = 0.0
    pressure_hpa = 0.0
    temperature = 0.0
    humidity = 0.0
    data = []
    for i in range (0xF7, 0xF7+8):
        data.append(bus.read_byte_data(i2c_address,i))
    pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
    temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
    hum_raw  = (data[6] << 8)  |  data[7]

    temperature = compensate_T(temp_raw)
    pressure = compensate_P(pres_raw)
    pressure_hpa = pressure / 100
    humidity = compensate_H(hum_raw)

    sensorData = {
        u'temperature': temperature,
        u'pressure': pressure_hpa,
        u'humidity': humidity,
        u'datetime': datetime.datetime.utcnow()
    }
    db.collection(u'raspi_temperature').add(sensorData)

def compensate_P(adc_P):
    global  t_fine
    pressure = 0.0

    v1 = (t_fine / 2.0) - 64000.0
    v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5]
    v2 = v2 + ((v1 * digP[4]) * 2.0)
    v2 = (v2 / 4.0) + (digP[3] * 65536.0)
    v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8)  + ((digP[1] * v1) / 2.0)) / 262144
    v1 = ((32768 + v1) * digP[0]) / 32768

    if v1 == 0:
        return 0
    pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125
    if pressure < 0x80000000:
        pressure = (pressure * 2.0) / v1
    else:
        pressure = (pressure / v1) * 2
    v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096
    v2 = ((pressure / 4.0) * digP[7]) / 8192.0
    pressure = pressure + ((v1 + v2 + digP[6]) / 16.0)
    print("pressure : %7.2f hPa" % (pressure/100))

    return pressure

def compensate_T(adc_T):
    global t_fine
    v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1]
    v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2]
    t_fine = v1 + v2
    temperature = t_fine / 5120.0
    print ("temp : %-6.2f degC" % temperature)

    return temperature

def compensate_H(adc_H):
    global t_fine
    var_h = t_fine - 76800.0
    if var_h != 0:
        var_h = (adc_H - (digH[3] * 64.0 + digH[4]/16384.0 * var_h)) * (digH[1] / 65536.0 * (1.0 + digH[5] / 67108864.0 * var_h * (1.0 + digH[2] / 67108864.0 * var_h)))
    else:
        return 0
    var_h = var_h * (1.0 - digH[0] * var_h / 524288.0)
    if var_h > 100.0:
        var_h = 100.0
    elif var_h < 0.0:
        var_h = 0.0
    print ("hum : %6.2f %" % (var_h))

    return var_h

def setup():
    osrs_t = 1          #Temperature oversampling x 1
    osrs_p = 1          #Pressure oversampling x 1
    osrs_h = 1          #Humidity oversampling x 1
    mode   = 3          #Normal mode
    t_sb   = 5          #Tstandby 1000ms
    filter = 0          #Filter off
    spi3w_en = 0            #3-wire SPI Disable

    ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode
    config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en
    ctrl_hum_reg  = osrs_h

    writeReg(0xF2,ctrl_hum_reg)
    writeReg(0xF4,ctrl_meas_reg)
    writeReg(0xF5,config_reg)


setup()
get_calib_param()


if __name__ == '__main__':
    try:
        readData()

    except KeyboardInterrupt:
        pass

raspi-firestore.py を実行し温度等コンソールに表示されればここまでOK

2. センサデータを firestore へ飛ばす

Firebase の準備

公式ガイドは下記。
https://firebase.google.com/docs/firestore/quickstart?hl=ja

以降、今回自分で実施した手順を記す。

Firebase プロジェクト作成

  • Firebase のコンソールに飛び、まずはプロジェクトを作成(プロジェクト名を命名)する。
    • →何でも良い。今回は「Raspi-iot」とした。
  • プランの選択
    • 今回は Spark プラン(無料)にした。
    • Blaze(従量制)との違いは Google Cloud Platform で拡張できるか否か
  • Googleアナリティクスの有効/無効を設定
    • →今回はとりあえず有効
  • 本番環境 or テストモード
    • →今回はテストモード
  • リージョン
    • →特にこだわりはないが近くがいいと思ったので asia-northeast1(東京)

ラズパイ側の準備

ここまでで、ラズパイでセンサから温湿度&気温を取得できるようになっているので、その値を Firestore へ飛ばすための準備をする

Firebase Admin SDK (firebase_admin) のインストール

Firebase Admin SDK についての公式アナウンスは下記を参照。
https://developers-jp.googleblog.com/2017/05/bringing-firebase-admin-to-python.html
以下、理解のためにざっくり書くと、
Firebase Admin SDK とは、(今回のケースでは)ラズパイに Firestore へデータを add する権限を持たせ、かつ add する命令を、Python スクリプトで記述できるようにするためのもの。という理解。

ラズパイへ Python3.6.11 のインストール

  • まずもって、firebase_admin はそもそも python 3.5 以上が必須らしいので、python 3.x 系をインストールする(condaやpyenv環境も考えたが今回はやらない)。
  • 後述するが 3.7 以上は openssl も VerUP しないといけなくなるので、3.6 系の Latest なバージョン (3.6.11) とした

ソースを落としてきてコンパイルすることになるのだが、まず先に、コンパイルに必要になるライブラリ libffi-dev libssl-dev openssl をインストールしておく。

ラズパイ側で実施
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install libffi-dev libssl-dev openssl

次にソースコードのダウンロード(任意の場所でOK)

ラズパイ側で実施
$ wget https://www.python.org/ftp/python/3.6.11/Python-3.6.11.tgz

コンパイルしていきます。

ラズパイ側で実施
$ tar zxvf Python-3.6.11.tgz
$ cd Python-3.6.11
$ ./configure
$ make
$ sudo make install

エラー無くインストールできたかどうか、バージョンを確認します。

ラズパイ側で実施
$ python3 -V
Python 3.6.11

3.6.11 になっていれば OK。
次にいよいよ firebase_admin のインストールだが、python コマンドでpython3 が実行されるようにしておかないと firebase_admin インストールに失敗する。下記のようにエイリアスを修正して python コマンドで python3 が実行されるようにする

ラズパイ側で実施
$ ls -l /usr/bin/python
lrwxrwxrwx 1 root root 9  3月 28  2015 /usr/bin/python -> python2.7 
#↑python コマンドが python2.7 に紐付いている

#↓今回インストールした python3 に python コマンドを紐付ける (python3 がどこにあるかは which python3 で検索)
$ ln -s /usr/local/bin/python3 /usr/local/bin/python

pip3 を upgrade しておく

ラズパイ側で実施
sudo pip3 install --upgrade pip   #  -> pip3がv18.1からv20.1.1になった

pip3 で firebase_admin をインストールする

ラズパイ側で実施
$ sudo pip3 install firebase_admin

grpcioライブラリのインストールからなかなか先に進まない。。。

ラズパイ側で実施
$ sudo pip3 install firebase_admin
〜(中略)〜
Using legacy setup.py install for grpcio, since package 'wheel' is not installed.
Using legacy setup.py install for msgpack, since package 'wheel' is not installed.
Installing collected packages: certifi, urllib3, idna, chardet, requests, six, protobuf, cachetools, pyasn1, pyasn1-modules, rsa, google-auth, pytz, googleapis-common-protos, grpcio, google-api-core, msgpack, cachecontrol, google-cloud-core, google-cloud-firestore, httplib2, uritemplate, google-auth-httplib2, google-api-python-client, google-resumable-media, google-cloud-storage, firebase-admin
    Running setup.py install for grpcio ... \

固まっているのかと不安になるが、ソースからビルドする必要があるので非常に時間がかかるとの情報を得る。とりあえず待ってみる。

5時間くらいかかったが無事完了

cron で定期実行

作成した python スクリプト(上述の hoge.py)を cron で定期実行することで定期的にセンサデータを firestore へ addする

3. Vue.js でセンサ情報をグラフにして表示するページを作成

基本的な事を確認
- Vue.js とは、JavaScript のフレームワークの 1 種
- JavaScript とはプログラミング言語
- 実質的に、私たちがブラウジングで体験できることのすべては、ブラウザの中で使われている JavaScript の処理によるもの
- そして、フレームワークとは、プログラミングにおいてアプリケーションプログラム等に必要な一般的な機能を、あらかじめ別に実装し、提供されているもの。開発者はそれを使うだけで済み、自分でわざわざ作らなくて良いので、ラクできる

→つまり、かなり乱暴ですが Vue.js を使うとウェブページが簡単につくれるということですね

Node.js 環境の準備

  • Vue.js で作ったグラフ表示ページを Firebase Hosting で公開したいわけで、つまり、Vue.js で作ったページはサーバサイドで実行させたい
  • しかし前述のとおり Javascript はブラウザ側で実行されるプログラム
  • しかしサーバサイドで実行させたい
  • しかも、開発はローカル環境(Mac)でやりたい。
  • そこで登場するのが、Node.js
  • Node.js は Webブラウザ以外(例えばサーバサイド)で JavaScript を実行するための環境の 1 つ
    • なぜサーバサイドで、、、、はあとで書く
  • Mac に Node.js 環境を構築すると、サーバサイドで動く Javascript をローカル(Mac)で開発でき、さらにはサーバで実行させることまで Mac の中で模擬でき、開発がはかどる
  • ローカルでの開発が終わったら後述する firebase deploy コマンド一発で Firebase にホスティングできる。
  • Node.js の世界では、開発が活発なのでアップデートが激しく、バージョンがすぐ変わると言うシチュエーションが頻繁に起こる
  • そうなると、Node.jsのバージョン管理を行ってくれるツールがあると便利である。
  • Node.js のバージョン管理ツールの 1 つに nodebrew というものがある。今回はそれをインストールし、それ(nodebrew)を使って Node.js をインストールする

と、言うことかと思う。。。

nodebrew のインストール

$ brew install nodebrew

nodebrew が使えるようになった

Node.jsのインストール(安定版)

$ nodebrew install-binary stable

下記のようにフォルダがないと怒られたら、、、(私は怒られた)

Warning: Failed to create the file
Warning: /Users/wakoudaisuke/.nodebrew/src/v14.5.0/node-v14.5.0-darwin-x64.tar.

フォルダを作っておいてあげればよい

$ mkdir -p ~/.nodebrew/src   # (-pはサブフォルダまで作る)

フォルダを作ったらもう一度 $ nodebrew install-binary stable を実行

Node.js のインストールに成功したら、以下のコマンドで、インストールされている Node.js のバージョンが一覧できるので、確認する

$ nodebrew ls
v14.5.0

current: none

インストール直後は current: none となっているため、必要なバージョン(ここでは1バージョンしかないが、複数バージョンインストールされている場合は複数出てくる)を決め、有効化する。

$ nodebrew use v14.5.0

もう一度 nodebrew ls を試すと

$ nodebrew ls
v14.5.0

current: v14.5.0

v14.5.0 が設定されました。

環境パスを通す

nodeが使えるように環境パスを通します。
bashの場合は以下。

$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile

bash以外で使いたいときは各シェルの適したファイルに追記します。
なんと macOS X 10.15 Catalina からは zsh がデフォルトになったので、zsh では以下になります。

$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zprofile

追記した後、ターミナルを再起動。
もしくは source コマンドで環境パスを読み込む。

最後に node が使えるか確認

$ node -v
v14.5.0

ここで v14.5.0 と表示されればインストール完了。

npmも使えるようになる。

$ npm -v
6.14.5

vue-cli の準備

vue-cli とは、
- vue.js で開発を行うにあたり必要な前準備を支援してくれるツール
- CLI とは Command Line Interface の略なので、つまり、コマンドラインでそれをやる
- 開発はプロジェクトという単位で行う
- vue-cli を使ってプロジェクトの作成を行う際、プロジェクトで利用する機能のインストールも一緒に行うことができる。質問に答える形で選択し、インストールをしてくれる。

以下、手順

npm で vue-cli をインストール

$ npm install -g @vue/cli

vue-cli には 2.x 系と 3.x 系があるらしい。
2.x 系は $ npm install -g vue-cli、3.x 系は $npm install -g @vue/cli となる。パッケージ名が異なる。
ちゃんとインストールされたか、以下で確認

$ vue -v
@vue/cli 4.4.6

バージョンが返ってくればOK

プロジェクトの作成

下記コマンドでプロジェクトを作成する

$ vue create <プロジェクト名>

今回は show-temperature という名前のプロジェクトにするので、下記のようにする。

$ vue create show-temperature

すると cli で各種初期設定が質問形式でなされるので、画面に従い質問に答える形で設定をほどこしていく。今回は全部デフォルトとした。
設定が終われば、show-temperature プロジェクトが完成。同名のディレクトリが作成される。
次のコマンドで、show-temperature プロジェクトをビルドして、ローカル(Mac)に勝手に立ち上げてくれる Web サーバで web ページが実行され、ブラウザで見る事ができるようになる。

$ cd <プロジェクト名>
$ npm run serve
 DONE  Compiled successfully in 2704ms             23:39:18

 I  Your application is running here: http://localhost:8080

(参考)https://qiita.com/Satachito/items/4a00b402970d657a88f3

ブラウザから http://localhost:8080 にアクセスすることで vue.js のテストページが見れれば成功。

グラフ描画ページを作成

今回見栄えを意識して vuetify も使ってみる。

$ vue add vuetify
〜初期設定を CLI に従って実施。今回はすべてデフォルト〜

** 作成した .vue ファイルは別途 github へ **

npm run serve でローカルで実行しながら開発、修正、を繰り返す。

firebase.js に api 情報をコピペ
main.js に firebase.js の読み込みを追記
参考サイト:https://qiita.com/ryo2132/items/2881d8223eb2ad3050a1

4. Firebase Hosting で公開

ローカルでページが完成したら、いよいよ公開の段階に入る。

Firebase CLI インストールと初期設定

まず、 Mac に Firebase CLI をインストールする。
Firebase CLI は、Firebase プロジェクトの管理、表示、デプロイを行うさまざまなツールを提供してくれる CLI とのこと。
npm コマンドで Firebase CLI をインストールできる。

$ npm install -g firebase-tools

https://qiita.com/rubytomato@github/items/b83caa01fc9c4993f526

今回の例でいうと show-temperature ディレクトリに移動し、まず

$ firebase login

する。するとブラウザが立ち上がり自分の Google アカウントを選択するページが現れるので、アカウントを選択すると、 Firebase CLI Login Successful となる。次に、

$ firebase init

で、Firebase の初期設定を行う (前述の vue init に似ている)

どのオプションを使うか、画面に従い選択しながら設定する。
 →使わないfunctionとかであとでエラーになると嫌なので、使うやつだけにしたほうがよい。今回は firestore と hosting のみを選択。

既存のプロジェクトを使用するか新規に作成するか聞かれるので既存を選択する。
 →今回は、前述した「Raspi-iot」を使う。既存プロジェクトは表示されるので選択する。

(base) Elon:raspi-temperature wakoudaisuke$ firebase init

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Users/wakoudaisuke/github/firebase/raspi-temperature

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, th
en Enter to confirm your choices. Firestore: Deploy rules and create indexes for Firestore, Hosting: Co
nfigure and deploy Firebase Hosting sites

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Please select an option: Use an existing project
? Select a default Firebase project for this directory: raspi-iot-c076f (Raspi-iot)
i  Using project raspi-iot-c076f (Raspi-iot)

=== Firestore Setup

Firestore Security Rules allow you to define how and when to allow
requests. You can keep these rules in your project directory
and publish them with firebase deploy.

? What file should be used for Firestore Rules? firestore.rules

Firestore indexes allow you to perform complex queries while
maintaining performance that scales with the size of the result
set. You can keep index definitions in your project directory
and publish them with firebase deploy.

? What file should be used for Firestore indexes? firestore.indexes.json

=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? public
  //↑ここ注意。vue.jsをデプロイするときは「dist」にする

? Configure as a single-page app (rewrite all urls to /index.html)? Yes 
  //↑ここも注意。vueが吐き出したdist配下にindex.htmlがあればYES。この次にfirebaseが上書きするか聞かれるがそれはNoでいいはず

✔  Wrote public/index.html


i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...
i  Writing gitignore file to .gitignore...

✔  Firebase initialization complete!

これで Firebase の初期化が完了。
vuefire もインストールする
vuefire はvue.js の data と firebase をバインディングしてくれるもの。

$ npm install firebase vuefire@next

ビルド

プロジェクトのルートディレクトリ(今回でいうと show-temperature)に移動し、下記コマンドを実行

$ npm run build

するとビルドされて、dist フォルダに公開用のファイル群が生成される。Mac の dist フォルダを確認してみるとよい。

デプロイ

生成された dist フォルダ内のファイル群をいよいよデプロイする。
(デプロイとは訳すと「配置」なので、ここでは、作ったページを Firebase Hosting に配置、すなわちインターネットで閲覧できる状態にすること、の意)
プロジェクトのルートフォルダで下記コマンドを実行

$ firebase deploy

=== Deploying to 'raspi-iot-c076f'...

i  deploying firestore, hosting
i  firestore: reading indexes from firestore.indexes.json...
i  cloud.firestore: checking firestore.rules for compilation errors...
✔  cloud.firestore: rules file firestore.rules compiled successfully
✔  firestore: deployed indexes in firestore.indexes.json successfully
i  firestore: latest version of firestore.rules already up to date, skipping upload...
i  hosting[raspi-iot-c076f]: beginning deploy...
i  hosting[raspi-iot-c076f]: found 1 files in public
✔  hosting[raspi-iot-c076f]: file upload complete
✔  firestore: released rules firestore.rules to cloud.firestore
i  hosting[raspi-iot-c076f]: finalizing version...
✔  hosting[raspi-iot-c076f]: version finalized
i  hosting[raspi-iot-c076f]: releasing new version...
✔  hosting[raspi-iot-c076f]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/raspi-iot-c076f/overview
Hosting URL: https://raspi-iot-c076f.web.app

firebase initをやってないフォルダでfirebase deployをやってもcould not locate firebase.jsonと言われて怒られる(私は最初怒られた)

実際にデプロイした web アプリがこちら
https://raspi-iot-c076f.web.app

その他、備忘録

vuefire で怒られたら

export 'default' (imported as 'VueFire') was not found in 'vuefire'

と怒れられたら、ここのとおりfirebase.jsを修正する
https://qiita.com/MuggyTea/items/5726019da614ae322dd3

firestore のルール編集

firestore のアクセス権設定は、ローカルにある firebase.rules を編集する。
web の firebase コンソールからも編集できるが、デプロイするたびにローカルの内容に上書きされる。
ルールの編集は下記を参照
https://firebase.google.com/docs/firestore/security/insecure-rules?hl=ja

Firebase Admin SDK と Cloud Functions は、引き続きデータベースにアクセスできます

参考にしたサイト

https://kojimainjp.hatenablog.com/entry/2018/12/26/202942
https://www.ultra-noob.com/blog/2020-03-21-やたら易しいvue-chart-js解説/

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

Flask+Docker+Vue.js+AWS...でゲームWebAppを作ってみた。

イントロで曲当てクイズ『イントロドン!』
コロナ自粛中のリモート呑みで遊べるゲームアプリが欲しくて作ってみました!
お酒呑みながら、みんなでガヤガヤ、時にはひとりでじっくりと楽しんでもらえたら嬉しいです。

●こちらのリンクから遊べます♪
http://introdon.akinko.work/

●Githubにソースコード、ゲームルールを公開しました
https://github.com/akiraseto/introdon

スクリーンショット 2020-07-15 14.33.44.pngスクリーンショット 2020-07-15 14.30.52.pngスクリーンショット 2020-07-15 14.30.27.png
スクリーンショット 2020-07-15 14.30.04.pngintrodon.akinko.work_user_entrance(iPhone 6_7_8) (1).png

ゲーム内容

楽曲のイントロ部分を聴いて曲名を当てるゲーム。4択問題で全10問出題。

遊べるモードは2つ

ひとりでじっくりモード

時間制限無しでじっくり解答

みんなで早押しモード

最大5人同時参加の早押し形式
正解順に高得点をGet
勝敗は合計点によるランキング発表!

PCブラウザ・スマホについて

PCブラウザ、スマートフォンともに対応していますが、
スマホ版は、出題ごとに「音楽を再生する」ボタンをタップしないとイントロが流れない仕様です。
(PCブラウザ版は自動で曲が流れる)

モバイル版Chrome、Safariのブラウザポリシーによりメディア要素の自動再生は禁止。
ユーザーの意図的な操作によってメディアを再生する。

残念ながらスマホ版は、みんなモードだとかなり不利になってしまいます。

技術内容

楽曲情報を「itunes api」から取得して問題を作成。
https://itunes.apple.com/search

楽曲情報をDBから取得して問題作成。
該当する楽曲がDB内で少ない場合、itunes APIから楽曲情報を取得し、被った情報を削除した上でDBに記録。同時に問題も作成します。

全体の流れ

  1. ゲームを開始する
  2. DBから該当する楽曲情報を取得
  3. 楽曲がある場合は、選択肢4×10問の問題を作成。
  4. 問題をsessionに渡す
  5. sessionから引き出して出題
  6. 解答内容をDBに記録
  7. 全10問解答後にDBからログを取得し、ランキング、正誤内容を表示

楽曲情報が足りない場合、
3. itunes APIから該当する楽曲情報を取得
4. duplicateする情報を削除
5. API取得した楽曲情報をDBに記録
6. 問題を作成(以下、同じ)

技術構成

主な技術構成は以下となります。

  • Flask
  • Docker
  • Nginx
  • Gunicorn
  • Vue.js + Jinja2
  • MariaDB
  • AWS
  • テスト(pytest, CircleCI)

Flask

MTVフレームワーク

Model, Template, ViewのMTVフレームワークに沿って開発。

ディレクトリ構成

  .
  ├── introdon
  │  ├── __init__.py
  │  ├── config_flask.py
  │  ├── models
  │  │  ├── games.py
  │  │  ├── logs.py
  │  │  ├── songs.py
  │  │  └── users.py
  │  ├── scripts
  │  ├── static
  │  ├── templates
  │  │  ├── _render_field.html
  │  │  ├── admin
  │  │  ├── games
  │  │  ├── layout.html
  │  │  └── users
  │  └── views
  │    ├── __init__.py
  │    ├── config_introdon.py
  │    ├── form.py
  │    ├── games.py
  │    ├── songs.py
  │    ├── users.py
  │    └── views.py
  ├── manage.py
  └── server.py

models

一言で、データベース連携の機能
単純なCRUDだけでなく、ビジネスロジックもModelにコーディングしてデータの取り回しを担当させる。

classmethod

インスタンスを作成して使い回したり、アトリビュートを使用することが無い場合は、その場で使えるクラスメソッドが便利です。

introdon/models/users.py
 # Classの中でデコレータをつけて宣言

@classmethod
def fetch_user_records(cls, users_id_list: list) -> list:
introdon/views/games.py
# インスタンス作らずにその場で使える

users_record_list = User.fetch_user_records(users_id_list)

traceback.print_exc

try exceptで例外表示に使用。例外が発生した際に、エラー詳細を表示させて原因が追いやすくなります。

introdon/models/games.py
#DB書き込み時

this_game = Game(**records)
db.session.add(this_game)
try:
  db.session.commit()
except:
#例外が発生したら

  db.session.rollback()
#DBロールバックし

  traceback.print_exc()
#エラーをスタックトレースして出力

エラーが発生した際、
①スタックトレースを抽出して、
②書式も整えた上で出力してくれます。
エラー解決にはprint(),loggerよりもおすすめです。

templates

TemplateはMTVの中で一番分かり易い描画(render)機能です。
その反面、フロントエンドフレームワークと連携しだすと一番複雑化しやすい箇所でもあります。

Jinja2を通して共通パーツをまとめた方が全体のコードがスッキリします。

introdon/templates/users/index.html
{% from "_render_field.html" import render_field %}
{% extends "layout.html" %}
{% block body %}

# 内容

{% endblock %}

ナビバーや、ヘッダーなど共通のパーツはlayout.htmlから読み込み
フォームなど使い回すパーツはmacroにして_render_field.htmlから読み込んでます

views

viewにビジネスロジックまで書いてしまうと、あっという間に肥大化&複雑化してスパゲティコードになってしまいます。(T_T)
APIのI/Oインターフェイスぐらいの認識にして、なるべくスリム化するように常に意識します。
viewはあくまで

  • 値の入出力
  • 処理全体の制御

この2つの役割に徹した方が良いです。
Flaskは比較的自由が効くフレームワークなのでいくらでもviewに書けてしまいますので。。

static

画像などのstaticなファイル置き場となります。
今回は、正解不正解などのSE音源を格納しました。

app.run設定:config_flask.py

flaskアプリ起動のapp.run()の設定ファイルです。
ファイル場所:introdon/config_flask.py

設定変数をファイルにまとめて一括で読み込ませています。
今回は環境変数によって分岐させて、読み込む変数をモードごとに変えています。

SECRET_KEY

データベースと、セッション情報を暗号化するためのキー。
分かり辛い方がいいので、os.random(24)で毎回値を変えてます。

DEBUG

DEBUGモードをオンにすると

  • ファイル変更したらappが自動リロードされる。
  • ブラウザにエラー内容を出力する
  • ツール:DebugToolBarが使用可能になる

開発では Trueにして本番では必ずFalseにします。

SQLALCHEMY_DATABASE_URI

SQLAlchemyを通してDBを利用する際の接続先です。
SQLAlchemyとMySQL(MariaDB)にはpymysqlのスキーマが必要。

SQLALCHEMY_RECORD_QUERIES

デバッグ出力用にSQL内容を記録します。
DebugToolBarで SQLを確認したいなら設定が必要。

よく使う基本機能

Flaskで高頻度でお世話になる機能をまとめてみました。

ルーター

route

関数とURLをバインドするデコレーター
POST,GETの許可を制御する

errorhandler

サーバーエラー発生時に処理を制御します

introdon/views/views.py
@app.errorhandler(500)
def internal_server_error(error):
 flash('Internal server error')
 return redirect(url_for('index'))

サーバーエラー500が発生したら
①flashメッセージを作り、
②indexメソッドバインドしているURLにリダイレクトさせる。

レスポンス

render_template

テンプレートhtmlを描画します。
jinja2を使ってhtml側に変数を渡すことができます。

flash

Flashメッセージは処理が終わった事や、エラーが発生した場合にユーザーに知らせるメッセージです。

  1. render時にview側でflash()に入力されたメッセージがセットされます。
  2. template側でget_flashed_messages()でメッセージを受け取り描画します。
introdon/templates/layout.html
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
  #内容をhtmlで出力
{% endfor %}
{% endif %}
{% endwith %}

複数受け取りも可能。
layout.htmlですべてのtemplateページにメッセージを出力できるよう設定しています。

redirect

任意のページにredirectすることもできます。
特にurl_forと組み合わせて使うことが多いです。

url_for

メソッド名を引数に取ると、バインドしているURLの文字列を返します。

return redirect(url_for('start_multi'))
  1. start_multiメソッドがバインドしているURLに文字列変換
  2. 文字列変換されたURLにリダイレクト

また、templateのhtmlでよく使うのが、
ディレクトリ名のstaticを第1引数、ファイル名の指定で素材ファイルとリンクすることができます。

url_for('static', filename='right.mp3')

staticディレクトリの'right.mp3'のリンク文字列を作成

jsonify

名前のとおりjson形式にシリアライズします。

request

ユーザーのrequest内容が格納されています。

よく使うのが、ユーザーがformに入力した内容の取得です。

introdon/views/songs.py
# song登録画面
@app.route('/admin/song', methods=['POST'])
def add_song():
 term = request.form['term']

POSTで送信されたformのname=termにユーザーが入力した値を取得します。

flask.requestはユーザーの様々なリクエスト内容を取得できるので使いみちが幅広いです。

# POSTでリクエストされているなら
if request.method == 'POST':
pass

#GETでクエリが'game_id'の内容を取得
game_id = request.args.get('game_id')

などなどあります。

session

ユーザーごとに、リクエスト(ページ)をまたいで情報を利用することができるSessionを手軽に利用できるようになります。
利用するには、app.run設定のSECRET_KEYを設定する必要があります。

DBとの接続

ORMとしてSQLAlchemyを使用しました。
MariaDB(MySQL)との接続にはpymysqlをスキームにかませる必要があります。

flask-script

DB環境の初期化をコマンド実行できるようにしました。
以下のコマンドで必要なTableが作成されDBを初期化します。

 python manage.py init_db

アプリ開始時にコマンド実行します。

flask-marshmallow

オブジェクトをシリアライズ化してくれます。
クイズ作成時に、SQLAlchemyでオブジェクト化された楽曲情報をjsonフォーマットに格納するために使用しました。

ログイン系の処理

flaskの拡張プラグインを利用してコーディング。

flask-login

  • ユーザーのログイン
  • ログアウト
  • そのユーザーはログイン状態か
  • ログインしている場合のユーザー情報取得

password_hash

  • パスワードをハッシュ化:generate_password_hash
  • ハッシュ化パスワードを確認:check_password_hash

フォームの処理

flask-WTF

フォームを作る際の便利な機能が揃っています。

  • form内容のvalidate
  • form項目の設定
  • CSRF対策

管理画面をかんたん設置したい

Adminユーザーから簡単な管理画面を閲覧・操作したい。
でも、イチからコーディングするのはシンドい。。これもflaskのプラグイン使います。

flask_admin

自分で作るのに比べると圧倒的に楽に管理画面が作れます。

DB内TableのCRUD操作がWebの管理画面から可能になります。
細かくカスタマイズすることも可能です。

スクリーンショット 2020-07-15 14.35.14.pngスクリーンショット 2020-07-15 14.35.24.pngスクリーンショット 2020-07-15 14.35.30.png

開発に便利なツール

使うと開発作業が捗るツールで、利用をおすすめします。

flask-debugtoolbar

DEBUGモードで利用できるようになる開発援助ツールです。
Webアプリ上にツールバーとして表示されます。変数や履歴、ヘッダー内容、SQL処理内容をブラウザから確認できるようになります。

Dockerでシステム構築

Webサーバー、アプリサーバー、DBサーバーをそれぞれDockerコンテナでシステム構築しました。
正確には、テスト用のDBコンテナがひとつ追加で4つのコンテナ構成をdocker-composeで構築しています。

システムのモードとして3種類あります。
Flaskコンテナの環境変数FLASK_ENVを変更することで以下の3つのモードでBuildします。

  • PROD
    • 本番モード
    • Gunicorn使用
  • DEV
    • 開発環境モード
    • DEBUGオン
    • debugtoolbarが表示
    • エラー時、内容がWEBに表示
    • コード変更でwebアプリが自動リロード
  • TEST
    • Func,Unitテストが自動実行
    • テスト用DBコンテナを使用
    • テストCoverageを表示

Nginx

Webサーバーに使用しました。

Nginxはメモリ使用量が小さく、処理も早い。
リバースプロキシの機能、ロードバランサ、HTTPキャッシュを持ち合わせています。
また、Gunicornは公式ページでNginxの使用を推奨しています。

Gunicorn

WSGIに使用しました。

uWSGIも検討しましたが、
①Gunicornの方が扱いやすくネット上の使用事例が多く(海外も含めて)、
②公式サイトも解説が丁寧だったのでGunicornを採用しました。

docker-composeを通して、FALSK_ENV=PRODなら以下から起動するようにしています。

flask_env.sh
gunicorn server:app -c gunicorn_setting.py

MariaDB

MySQLと完全互換があり、MySQLより処理が早い、MariaDBを使用しました。
完全互換なのでSQLAlchemyや、スキーマなどMySQLの設定がそのまま使えます。

フロントエンド

Jinja2 + Vue.js + Bootstrapで作成しました。

MPA(Multi Page Application)としてJinja2を基本に、Vue.jsをCDNで読み込んで組み合わせて実装。
CSSはBootstrapを利用してレスポンシブデザインにしてPC、スマホの両ブラウザ対応にしています。

FontAwesomeを初めて使用したのですが、簡単なタグのみで手軽にアイコンを組み込めるのでおすすめです。
https://fontawesome.com/

AWS

1つのEC2インスタンスに全てのDockerコンテナを構築しました。

順当ならECSを使って、コンテナ毎にEC2インスタンスに分配して制御管理するのがベストプラクティスだと思います。
1つのEC2インスタンスに収めた理由として
①AWS無料枠内に収めたい
②クラスタリングが必要なほどのアクセス数ない
③趣味なので強い可用性も堅牢性も求めない
上記理由で、今回は見送りました。

ただ、ECSは思ったよりも導入しやすく便利そうなので今後の別アプリで検討してみたいです。
さらに、Kubenatesを導入しても面白そうです。

テスト

pytestで書いています。
python標準のunittestよりもシンプルにかけて読みやすく、プラグインも豊富なためpytestを採用しました。

CIには CircleCIを利用しました。
DockerのDBコンテナを使ったテストはせずに、mock利用のテストだけをCircleCI上で実行しています。

終わりに

楽しかった。。
Flaskは先人達の優れたプラグインが多くあり、組み合わせることでやれることが加速的に多くなっていく。。
その「やれることが増えていく感」が気持ちいいフレームワークです。皆様も機会があればぜひ触ってみてください!

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

Vueの動的コンポーネントで爆詰まりした

Vueの<component>コンポーネント

動的にコンポーネントを切り替える必要があったので、vue dynamic componentと検索してヒットした公式のガイド通り<component>を使ってみたが、プロパティの指定方法で詰まった挙句、別の方法で実装した。

<component>の欠陥

<component>:isでコンポーネントを指定したはいいものの、そのコンポーネントではプロパティを要求していた。もちろん<component>では細かな操作ができるはずもなく、この記事のように全くスマートではない方法で実装する必要がある。しかも当環境で実装してもやたらエラーが発生してろくに使えない。

v-if

動的コンポーネントで検索して公式がああ言ってるのだから、これ以外じゃ方法がないと思い込んでたので、にっちもさっちもいかなくなった。仕方がないので後回しにして他のコンポーネントを実装していった。v-ifディレクティブを見つけた。

条件付きレンダリング

言うまでもないが、v-ifで指定した理論値がtrueの場合、コンポーネントをレンダリングするディレクティブである。

<div>
      <hoge v-if="content === 'hoge'" :hoge="hoge"></hoge>
      <fuga v-if="content === 'fuga'" @fuga=”fuga"></fuga>
      <piyo v-if="content === 'piyo'"></piyo>
</div>

contentが特定の文字列に一致した場合にのみコンポーネントが生成しレンダリング、一致しなくなったコンポーネントは破棄されるようになる。タブを使ったUIでも十分に機能するだろう。

<component>はいつ使うのか

多分一生訪れない。<component>を使うケースあればコメントしてください。

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

Vuex の使い方(初級編)

Vuex とは

スクリーンショット 2020-07-15 14.17.50.png

Vuex とは Vue.js アプリケーションのための 状態管理パターン + ライブラリ。
大規模なプロジェクト開発を行う際にコンポーネントごとで共通して利用するデータを効率的に
状態管理をすることを可能にするライブラリーです。

初級編としてコンポーネント内で表示するところまでをこちらの記事で記載します。

ファイルディレクトリー

-- views
     |
     -- Home.vue
-- store
     |
     -- index.js
--main.js

手順① Vuex のインストール

npm install vuex

手順② main.js に記述

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'// 追加

Vue.config.productionTip = false

new Vue({
  router,
  store, //追加
  render: h => h(App)
}).$mount('#app')

main.jsにてstoreを使用できるように追加します。

手順③ storeを作成

今回は、storeフォルダー直下に、index.jsを作成し、その中で状態管理をしようと思います。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 2 //状態を指定
  },
  getters: { //gettersには メソッドを記述
    doubleCount: state => state.count * 2, 
    tripleCount: state => state.count * 3
  },
  mutations: {

  },
  actions: {

  },
  modules: {

  }
})

今回は初期値としてstateに数字の2。
gettersの中には、それを2倍にするメソッドと、3倍にするメソッドを記載しております。

手順④ View で表示する

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    <p>{{ count }}</p>
    <p>{{ doubleCount }}</p>
    <p>{{ tripleCount }}</p>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</template>

export default {
  computed: {
    // * store.js から 状態を呼び込み
    count() {
      return this.$store.state.count
    },
    // * getters から関数を取得する
    doubleCount() {
      return this.$store.getters.doubleCount;
    },
    tripleCount() {
      return this.$store.getters.tripleCount;
    }
  },
  name: 'Home',
  components: {
    HelloWorld
  },
  methods: {
    increment(){
      this.$store.state.count++;
    },
    decrement(){
      this.$store.state.count--;
    }
  }
}

computedプロパティの中に

    count() {
      return this.$store.state.count
    },

記述しているこちらで、storeのstateの値を読んでいます。そしてそれをcountで呼べるようにしています。

    doubleCount() {
      return this.$store.getters.doubleCount;
    },
    tripleCount() {
      return this.$store.getters.tripleCount;
    }

上記二つでは、gettersで作成した2倍にする処理の関数と、3倍にする関数をこちらで
読んでいます。

ただ、この記述だとだいぶ冗長的ですよね。

それをmapGettersを使用し完結に書くことができます。

mapGetters

import { mapGetters } from 'vuex'
exportdefault {
  computed: {
    // ? オブジェクトの中に記載する場合はスプレッド演算子を使用する
    ...mapGetters(["doubleCount", "tripleCount"]),
  },

computedプロパティの中に{}(オブジェクト型)にすることで複数値を入れられるようにしています。

...mapGetters(["doubleCount", "tripleCount"])

配列[]のなかに、gettersにおいた関数を複数指定するだけで、使用することができます。

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

Vuex の使い方 mapGetters (初級編)

Vuex とは

スクリーンショット 2020-07-15 14.17.50.png

Vuex とは Vue.js アプリケーションのための 状態管理パターン + ライブラリ。
大規模なプロジェクト開発を行う際にコンポーネントごとで共通して利用するデータを効率的に
状態管理をすることを可能にするライブラリーです。

初級編としてコンポーネント内で表示するところまでをこちらの記事で記載します。

ファイルディレクトリー

-- views
     |
     -- Home.vue
-- store
     |
     -- index.js
--main.js

手順① Vuex のインストール

npm install vuex

手順② main.js に記述

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'// 追加

Vue.config.productionTip = false

new Vue({
  router,
  store, //追加
  render: h => h(App)
}).$mount('#app')

main.jsにてstoreを使用できるように追加します。

手順③ storeを作成

今回は、storeフォルダー直下に、index.jsを作成し、その中で状態管理をしようと思います。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 2 //状態を指定
  },
  getters: { //gettersには メソッドを記述
    doubleCount: state => state.count * 2, 
    tripleCount: state => state.count * 3
  },
  mutations: {

  },
  actions: {

  },
  modules: {

  }
})

今回は初期値としてstateに数字の2。
gettersの中には、それを2倍にするメソッドと、3倍にするメソッドを記載しております。

手順④ View で表示する

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    <p>{{ count }}</p>
    <p>{{ doubleCount }}</p>
    <p>{{ tripleCount }}</p>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</template>

export default {
  computed: {
    // * store.js から 状態を呼び込み
    count() {
      return this.$store.state.count
    },
    // * getters から関数を取得する
    doubleCount() {
      return this.$store.getters.doubleCount;
    },
    tripleCount() {
      return this.$store.getters.tripleCount;
    }
  },
  name: 'Home',
  components: {
    HelloWorld
  },
  methods: {
    increment(){
      this.$store.state.count++;
    },
    decrement(){
      this.$store.state.count--;
    }
  }
}

computedプロパティの中に

    count() {
      return this.$store.state.count
    },

記述しているこちらで、storeのstateの値を読んでいます。そしてそれをcountで呼べるようにしています。

    doubleCount() {
      return this.$store.getters.doubleCount;
    },
    tripleCount() {
      return this.$store.getters.tripleCount;
    }

上記二つでは、gettersで作成した2倍にする処理の関数と、3倍にする関数をこちらで
読んでいます。

ただ、この記述だとだいぶ冗長的ですよね。

それをmapGettersを使用し完結に書くことができます。

mapGetters

import { mapGetters } from 'vuex'
exportdefault {
  computed: {
    // ? オブジェクトの中に記載する場合はスプレッド演算子を使用する
    ...mapGetters(["doubleCount", "tripleCount"]),
  },

computedプロパティの中に{}(オブジェクト型)にすることで複数値を入れられるようにしています。

...mapGetters(["doubleCount", "tripleCount"])

配列[]のなかに、gettersにおいた関数を複数指定するだけで、使用することができます。

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

Vuex の導入 / 使い方 mapGetters (初級編)

Vuex とは

スクリーンショット 2020-07-15 14.17.50.png

Vuex とは Vue.js アプリケーションのための 状態管理パターン + ライブラリ。
大規模なプロジェクト開発を行う際にコンポーネントごとで共通して利用するデータを効率的に
状態管理をすることを可能にするライブラリーです。

初級編としてコンポーネント内で表示するところまでをこちらの記事で記載します。

ファイルディレクトリー

-- views
     |
     -- Home.vue
-- store
     |
     -- index.js
--main.js

手順① Vuex のインストール

npm install vuex

手順② main.js に記述

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'// 追加

Vue.config.productionTip = false

new Vue({
  router,
  store, //追加
  render: h => h(App)
}).$mount('#app')

main.jsにてstoreを使用できるように追加します。

手順③ storeを作成

今回は、storeフォルダー直下に、index.jsを作成し、その中で状態管理をしようと思います。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 2 //状態を指定
  },
  getters: { //gettersには メソッドを記述
    doubleCount: state => state.count * 2, 
    tripleCount: state => state.count * 3
  },
  mutations: {

  },
  actions: {

  },
  modules: {

  }
})

今回は初期値としてstateに数字の2。
gettersの中には、それを2倍にするメソッドと、3倍にするメソッドを記載しております。

手順④ View で表示する

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    <p>{{ count }}</p>
    <p>{{ doubleCount }}</p>
    <p>{{ tripleCount }}</p>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</template>

export default {
  computed: {
    // * store.js から 状態を呼び込み
    count() {
      return this.$store.state.count
    },
    // * getters から関数を取得する
    doubleCount() {
      return this.$store.getters.doubleCount;
    },
    tripleCount() {
      return this.$store.getters.tripleCount;
    }
  },
  name: 'Home',
  components: {
    HelloWorld
  },
  methods: {
    increment(){
      this.$store.state.count++;
    },
    decrement(){
      this.$store.state.count--;
    }
  }
}

computedプロパティの中に

    count() {
      return this.$store.state.count
    },

記述しているこちらで、storeのstateの値を読んでいます。そしてそれをcountで呼べるようにしています。

    doubleCount() {
      return this.$store.getters.doubleCount;
    },
    tripleCount() {
      return this.$store.getters.tripleCount;
    }

上記二つでは、gettersで作成した2倍にする処理の関数と、3倍にする関数をこちらで
読んでいます。

ただ、この記述だとだいぶ冗長的ですよね。

それをmapGettersを使用し完結に書くことができます。

mapGetters

import { mapGetters } from 'vuex'
exportdefault {
  computed: {
    // ? オブジェクトの中に記載する場合はスプレッド演算子を使用する
    ...mapGetters(["doubleCount", "tripleCount"]),
  },

computedプロパティの中に{}(オブジェクト型)にすることで複数値を入れられるようにしています。

...mapGetters(["doubleCount", "tripleCount"])

配列[]のなかに、gettersにおいた関数を複数指定するだけで、使用することができます。

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

Vue.js v-forの使い方

Vueのv-forの使い方について理解したこと

前回の記事で理解が怪しいまま終わった「v-for」について
シンプルなコードに書くと理解出来た。
前回の記事
Vue.jsを初めて触ってみた

以下今回の内容↓

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>Vueの練習</h1>
<div id="main">
    <p>{{test}}</p>
    <ul>
        <li v-for='item in list'>{{item}}</li>
        <!-- Vueの中で定義したlistという変数に指定された配列を繰り返し処理する -->
        <!-- itemは配列の中身を表示する場所を{}で指定している -->
    </ul>
</div>


<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>

new Vue({
    el:'#main',
    data:{
        test:'helo',
        list:[1,2,3,4]
    }
})


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

ポイントはココ!↓
html側
<li v-for='item in list'>{{item}}</li>
<!-- Vueの中で定義したlistという変数に指定された配列を繰り返し処理する -->
<!-- itemは配列の中身を表示する場所を{}で指定している -->

js側

new Vue({
el:'#main',
data:{
test:'helo',
list:[1,2,3,4]
}
})

つまり
〇〇 in ✖️✖️の
「✖️✖️」でjs側のdata内の配列を格納した変数を指定。
「〇〇」はその配列の内容を表示する場所をhtml側で{}で括って指定。

ここを押さえると理解できそうだ。

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

Vue.jsを初めて触ってみた

Vueをこれから学習することにしたので、その備忘録

2日勉強して得た知識の紹介

私について

jsを勉強し始めて2ヶ月程度
classとかわからないレベル

参考にしたもの

Vue公式ドキュメント
https://jp.vuejs.org/
ハンズオンはすぐ挫折。流して最後まで読みました。

キータの記事
https://qiita.com/kiyokiyo_kzsby/items/ce9fe8b72953584fecee
https://qiita.com/don-bu-rakko/items/9a1f7e3a18f526e446ea
↑こちらの記事とてもよかったです。
公式ドキュメントを読んでからyoutubeをみると理解が少し理解出来るようになりました。

キータのyoutubeの成果物

スクリーンショット 2020-07-15 10.15.33.png

axiosで読み取ったjson形式のAPIを使用し、画像の追加、削除が出来るもの。

コードは以下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>

        table {
  border-collapse: collapse;
  text-align: left;
  line-height: 1.5;
}

th {
  width: 150px;
  padding: 10px;
  font-weight: bold;
  border: 1px solid #ccc;
}

td {
  width: 350px;
  padding: 10px;
  border: 1px solid #ccc;
}

.yes {
  background-color: #90ee90
}

.no {
  background-color: #ff7f50
}
    </style>
</head>
<body>
    <h1>Vueの練習</h1>

    <!-- ここから -->

    <div id="app">
      <div>
        <label>
        質問を入力:
        <input type="text" v-model='question'>
        </label>
        <button v-on:click='add()'>送信</button>
        <!-- ボタンを押すとadd関数を実行 -->
      </div>

      <!-- 回答 -->
      <table>    
        <thead>
          <tr>
            <td>日時</td>
            <td>質問</td>
            <td>回答</td>
            <td>画像</td>
            <td>削除</td>
          </tr>
        </thead>
        <tbody>
          <tr v-for='item in items'>
            <td>{{item.date | date-filter}}</td><!-- 日時 -->
            <td>{{item.question}}</td><!-- 質問 -->
            <td>{{item.answer}}</td><!-- 回答 -->
            <td><img v-bind:src="item.image" width="100" height="100"></td>
            <td><button v-on:click="remove()">削除</button></td>
          </tr>

          <!-- 合計行 -->
          <tr>
            <td colspan="5">合計:{{total}} 件</td>
          </tr>
        </tbody>
      </table>
    </div>

 <script src="https://cdn.jsdelivr.net/npm/vue"></script>
 <script src="https://unpkg.com/axios@0.12.0/dist/axios.min.js"></script>
<script>
Vue.filter('date-filter',function(val){
    if(!val)return;
    return[val.getFullYear(),(val.getMonth()+1),val.getDate()]
    .join('-')+''+'/'+
    [val.getHours(),val.getMinutes(),val.getSeconds()].join(':');
})

new Vue({
    el:'#app',
    data:{
        // item:{},
        items:[],
        question:''
    },
    methods:{//関数を書くときはmethodsを使用する
        add:function(){
            console.log(this.question);
            let vm = this;
        axios.get('https://yesno.wtf/api')
        .then(function(response){
          console.log(response.data);
          console.log(response);
        //   vm.item = response.data
        //   vm.item.date = new Date();
        //   vm.item.question = vm.question;
             let item = {
                 date: new Date(),
                 question: vm.question,
                 answer: response.data.answer,
                 image:response.data.image
             };
             vm.items.push(item);
        });
      },
      remove: function(index) {
    this.items.splice(index, 1);
      }

    },
    computed:{
        total:function(){
            return this.items.length;
        }
    }
});



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

ざっくり解説

el:html側の場所を指定する(Vueを使用するところ)

data:Vueで使用する変数を格納する場所。

→question:
v-modelで指定することですることで双方向バインディングを作成
コードに記載していないが、htmlファイル内で

<p>{{question}}</p>
を打つとinputタグ内で入力した文字がリアルタイムでpタグ内に表示される。

→items:
<tr v-for='item in items'>

上記のように定義することで配列の中の内容をfor文で回し、addをクリックするごとに追加で表が表示されるようにしている。
ちなみに'item in items'の記載は該当する項目を全て変更すれば'a in b'にしても動く。

methods:関数を定義するところ ※()を忘れずつけよう※

computed:関数の戻り値を変数として利用する ※returnを忘れずにつけよう※

computedは算出プロパティらしいのだが、その理解はまだ出来ていない。念のため、公式ドキュメントを貼っておく。
https://jp.vuejs.org/v2/guide/computed.html#%E7%AE%97%E5%87%BA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3

2020/7/15の時点での学びは以上です。
所感:公式ドキュメントを軽くでも一読してから動画や書籍を見ると良いと感じた。

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

Vue.jsでthis.$options.methods.function()を使ってはいけない理由

Vue.jsのmethods

通常methodsに書いた関数のthisはVueインスタンスを参照するので、this.$dataなどにアクセスできます。
しかし、JavaScriptのthisは関数の親になるという決まりがあるため、this.$options.methods.function()という風にアクセスするとthismethodsになり、エラーが発生します。

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

Vue.js備忘録(1)

Vue.jsの勉強過程の記録(1)

目標

ボタンを押すとテキストが切り替わる

index.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="app">
        <p>{{ name }}</p>
        <button v-on:click="change">{{ name2 }}</button>
        <!-- このボタンを押すとpタグの中身が切り替わる -->
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <!-- vue.jsの読み込み -->
    <script src="index.js"></script>
  </body>
</html>
index.js
// インスタンスの生成
var app = new Vue({
  el: "#app",
//適用範囲の指定(id="app")
  data: {
    name: 'before',
    name2: 'change'
    //nameの中身を'before',name2の中身を'before2'に
  },
  methods: {
    change: function(){
      this.name = 'after';
      //changeメソッドの設定
      //nameの中身を'after'に変える
    }
  }
})

jqueryに比べて分かりやすく書けるなと思いました。
まだまだ初歩中の初歩ですが、頑張ります。

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