20200117のvue.jsに関する記事は12件です。

Vue.jsでスライドショーを作成したお話

結論

  • Vue.js上にスライドショーを実装するフレームワークeagle.jsを使うだけでそれっぽいスライドショーができる
  • ホスティングはFirebaseで爆速アップロード
  • 出来上がったサイトは これ ※レスポンシブ対応していません
  • ソースはここ

発端

弊社では新入社員のサポートとして、一年間先輩社員のメンターがアサインされ、月次で状況把握(カフェーで雑談)を行っている。
年末に新入社員の親御さんに向けてお宅のお子さんこんなことやってるんすよ。という先輩からのメッセージを載せた色紙を送り付けるサプライズイベントが行われる。
これが原因で転職する人がいそう
ただメッセージを書くのはナンセンスなのでどうせならウェブアプリの形で公開してやろうと思いつき実装。
結果として謎のQRコードとここにアクセスしろというメッセージが親御さんに届いた。

技術セット

  • Vue.js
  • eagle.js
  • Firebase

eagle.jsのポイント

タグで区切るだけで1ページ作成できる

  <slide :steps="1" enter="slideInDown">
    <eg-transition enter="fadeInDown" leave="fadeOut">
      <div v-if="step >= 1">
       スライドだよ!
      </div>
    </eg-transition>
  </slide>

slide1.gif

ページめくりのアニメーションも柔軟に設定可能

slide2a.gif

感想

それなりに好評でした。どうせなら文字じゃなくて成果物で語りたいよね。
コンポーネントとして分離できるしちょっとした動きをつけたい時に使えそう。

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

VueとFirebaseと使ってAtCoderのAC数をツイートするウェブアプリケーションを作った

概要

AtCoder上でのAC数を1日1回ログインしたツイッターアカウントでツイートしてくれるdevotterというアプリケーションを作成したよというお話

リンク

スライド

Github

ソースコード(フロントエンド)

<template>
  <v-container pa-12 fill-height>
    <v-layout align-center text-center wrap>
      <v-flex xs12>
        <v-alert v-model="alert" type="error" dismissible>{{errorMessage}}</v-alert>
        <v-alert v-model="success" type="success" dismissible>SUCCESS!</v-alert>
        <v-img :src="require('../assets/thai monk.svg')" class="my-3" contain height="200"></v-img>
        <h1>「Devotter」</h1>
        <h2>
          その精進
          <br />ツイートしませんか
        </h2>
        <br />
        <h5>このアプリケーションは、1日1回AtCoderでのAC数をTwitterにツイートしてくれるアプリケーションです。</h5>
        <br />
        <v-form>
          <v-text-field
            @change="changeField"
            type="text"
            v-model="atcoderId"
            v-if="login"
            label="AtCoderID"
            color="#00acee"
            required
          ></v-text-field>
        </v-form>
        <v-btn
          @click="signin"
          v-bind:disabled="isnull"
          v-show="login"
          color="#00acee"
          rounded
        >Sign In To Twitter</v-btn>
        <v-btn @click="logout" v-show="!login" color="#00acee" rounded>Log Out from Twitter</v-btn>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
import firebase from "firebase";
import axios from "axios";

export default {
  name: "guestUserScreen",
  methods: {
    logout: function() {
      firebase
        .auth()
        .signOut()
        .then(()=>{
          this.success=true;
          this.login=true;
        })
        .catch(()=>{
          this.alert=true;
          this.errorMessage="サインアウトに失敗しました。"
        });
    },
    signin: function() {
      var db = firebase.firestore();
      var document = this.atcoderId;
      axios
        .get(this.userApi + this.atcoderId)
        .then(response => {
          this.userInfo = response;
          const provider = new firebase.auth.TwitterAuthProvider();
          this.success = true;
          firebase
            .auth()
            .signInWithPopup(provider)
            .then(function(result) {
              let token = result.credential.accessToken;
              let secret = result.credential.secret;
              db.collection("users")
                .doc(document)
                .set({
                  accessToken: token,
                  accessTokenSecret: secret
                });
            })
            .catch(function() {
              this.alert = true;
              this.errorMessage = "ERROR!";
            });
        })
        .catch(() => {
          this.alert = true;
          this.errorMessage = "無効なAtCoderID名です。";
        });
    },
    changeField: function() {
      this.atcoderId ? (this.isnull = false) : (this.isnull = true);
    }
  },
  created: function() {
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
        this.login = false;
      }
    });
  },
  data: function() {
    return {
      atcoderId: "",
      isnull: true,
      userInfo: "",
      userApi: "https://kenkoooo.com/atcoder/atcoder-api/v2/user_info?user=",
      alert: false,
      errorMessage: "",
      success: false,
      login: true
    };
  }
};
</script>

ソースコード(バックエンド)

//import
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const serviceAccount = require('./atcontributter-firebase-adminsdk-5p86i-6e55085ef8.json');
const axios = require('axios');
const twitter = require('twitter');
const cors = require('cors')({ origin: true });
const decycle = require('json-decycle').decycle;
const retrocycle = require('json-decycle').retrocycle;

//initialize
const adminConfig = JSON.parse(process.env.FIREBASE_CONFIG);
adminConfig.credential = admin.credential.cert(serviceAccount);
admin.initializeApp(adminConfig);

//create instance
const firestore = admin.firestore();
const bucket = admin.storage().bucket();

//global
let consumerKey;
let consumerKeySecret;
let accessToken;
let accessTokenSecret;
let receiveAc;
let receiveNewAcString;
let receiveNewAc;
let senderAc;
let uploadPath;
let problemCount;
let newProblemCount;

//main
exports.devotterCronJob = functions.region('asia-northeast1').https.onRequest(async (request, response) => {
    cors(request, response, () => {
        response.set('Access-Control-Allow-Origin', 'http://localhost:5000');
        response.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS, POST');
        response.set('Access-Control-Allow-Headers', 'Content-Type');
    });
    try {
        //firebase storageから昨日のAtCoderAC情報が入ったjsonを取得する
        const receiveJson = await bucket.file('ac.json').download();
        receiveAc = JSON.parse(receiveJson);

        //kenkooooさんのAtCoderAPIを使って本日のAtCoderAC情報をaxiosで取得
        const receiveNewJson = await axios.get('https://kenkoooo.com/atcoder/resources/ac.json', { headers: { 'accept-encoding': 'gzip' } });
        receiveNewAcString = JSON.stringify(receiveNewJson, decycle());
        receiveNewAc = JSON.parse(receiveNewAcString, retrocycle()).data;

        //firestoreからtwitter apiのconsumerKeyとconsumerKeySecretを取得
        const getConsumerKeyQuerySnapShot = await firestore.collection('api').doc('keys').get();
        consumerKey = getConsumerKeyQuerySnapShot.data().consumerKey;
        consumerKeySecret = getConsumerKeyQuerySnapShot.data().consumerKeySecret;

        //firestoreからツイートに必要なユーザー情報を取得
        const getUserDataQuerySnapShot = await firestore.collection('users').get();
        getUserDataQuerySnapShot.forEach(async document => {
            try {
                //取得したUserQuerySnapShotからaccessTokenとaccessTokenSecretを取得
                const getAcceseTokenQuerySnapShot = await firestore.collection('users').doc(document.id).get();
                accessToken = getAcceseTokenQuerySnapShot.data().accessToken;
                accessTokenSecret = getAcceseTokenQuerySnapShot.data().accessTokenSecret;

                //昨日のjsonデータ中のfirestoreに登録しているユーザーのAC数を抽出
                for (const element in receiveAc) {
                    if (receiveAc[element].user_id === document.id) {
                        problemCount = receiveAc[element].problem_count;
                        break;
                    }
                }

                //今日のjsonデータ中のfirestoreに登録しているユーザーのAC数を抽出
                for (const element in receiveNewAc) {
                    if (receiveNewAc[element].user_id === document.id) {
                        newProblemCount = receiveNewAc[element].problem_count;
                        break;
                    }
                }

                //twitterクライアントにkeyを登録
                const client = new twitter({
                    consumer_key: consumerKey,
                    consumer_secret: consumerKeySecret,
                    access_token_key: accessToken,
                    access_token_secret: accessTokenSecret
                });

                //twitter apiを使ってツイート
                let tweetMessage = '今日の' + document.id + 'さんのAC数は' + (newProblemCount - problemCount) + 'でした。\n#devotter'
                client.post('statuses/update', {
                    status: tweetMessage
                }, (error) => {
                    if (!error) {
                        response.status(200).send('success!');
                    }
                    else {
                        response.status(500).send(error);
                    }
                });

                //今日取得したAC数をfirebase storageにアップロード
                senderAc = JSON.stringify(receiveNewAc);
                uploadPath = 'ac.json';
                bucket.file(uploadPath).save(senderAc);
            }
            catch (error) {
                response.status(500).send(error);
            }
        });
    }
    catch (error) {
        response.status(500).send(error);
    }
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Next.js/Nuxt.js】Nuxt.jsで言うfetch()やasyncData()のNext.js版→getInitialProps()

Next.jsのgetInitialProps()

Next.jsでも、Nuxt.jsで言うところのfetch()またはasyncData()にあたる処理を見つけました。
getInitialProps(context)です。

詳しくは、getInitialProps | 公式サイト(英語)に記載があります。
画面表示前にfetchができるとのことです。

※もし認識に誤りがある場合はコメント頂けるとありがたいです。

説明

getInitialProps()の引数は、Context Object | 公式サイト(英語)です。

jsxのthis.propsgetInitialProps()でreturnしたオブジェクトのプロパティが入ってきます。

以下は、上記の公式サイトより簡素に書き直したものです。

import React from 'react'

class Page extends React.Component {
  // 引数「context」については上述
  static async getInitialProps(context) {
    return { name: 'Hanako' }
  }

  render() {
    // this.propsにプロパティ「name」が入ってくる
    return <div>Name is {this.props.name}</div> 
  }
}

export default Page

参照URL

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

【Next.js/Nuxt.js】Nuxt.jsで言うasyncData()のNext.js版→getInitialProps()

Next.jsのgetInitialProps()

Next.jsでも、Nuxt.jsで言うところのasyncData()にあたる処理と思われる処理を見つけました。
getInitialProps(context)です。

詳しくは、getInitialProps | 公式サイト(英語)に記載があります。
画面表示前にfetchができるとのことです。

※もし認識に誤りがある場合はコメント頂けるとありがたいです。

説明

getInitialProps()の引数は、Context Object | 公式サイト(英語)です。

jsxのthis.propsgetInitialProps()でreturnしたオブジェクトのプロパティが入ってきます。

以下は、上記の公式サイトより簡素に書き直したものです。

import React from 'react'

class Page extends React.Component {
  // 引数「context」については上述
  static async getInitialProps(context) {
    return { name: 'Hanako' }
  }

  render() {
    // this.propsにプロパティ「name」が入ってくる
    return <div>Name is {this.props.name}</div> 
  }
}

export default Page

参照URL

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

plugin実践編:vue-scrolltoでスムーズにスクロール

vue-scrolltoとは

ページ内リンク(アンカーリンク)で
スクロールしながら指定場所に飛びます?‍♀️?
【公式】
https://www.npmjs.com/package/vue-scrollto

比較: vue-scrollto未使用

味気ない?
nuxt-linkを使うと1回しか機能しない
a hrefでやれば解決するけど…
どうせならnuxt-link使いたいじゃない

anchor.gif

index.vue
<template>
 <div class="page">
   <nuxt-link to="#anchor">
     下に飛ぶ
   </nuxt-link>
   <div id="anchor">
     とべた!
   </div>
 </div>
</template>

比較: vue-scrollto使用

スクロールバーに着目?
スルスルっとスクロール?
通常と違い、何回でもとべますね。
コードはstep順に記載していきます✍️

scrollto.gif

step1: インストール

ターミナル
$ npm i vue-scrollto
file
pages/
--| sample.vue

plugins/
--| vue-scrollto.js

nuxt.config.js

step2: /pluginsにjsファイルを追加

【vue-scrollto.js】
・Nuxt.jsでpluginを使用時の書き方
https://ja.nuxtjs.org/api/configuration-plugins/

・オプションは公式の
 Options項目で確認できます。

 duration: スクロール継続時間
 easing: 速度の緩急
 offset: 遷移後の位置調整
     少しだけ上に設定すると⭕️

easingに関してはここが参考になります!
https://note.com/ritar/n/n5e8ed0e07917

vue-scrollto.js
import Vue from 'vue'
import VueScrollTo from 'vue-scrollto'

Vue.use(VueScrollTo, {
 duration: 700,
 easing: [0, 0, 0.1, 1],
 offset: -100,
})

step3: nuxt.config.jsのpluginsに記載

nuxt.config.js
plugins: [
  '~plugins/vue-scrollto'
],

vue-scrollto.jsに記載したoptionsは
modulesに記載することもできます。
vue-scrollto公式のNuxt.js項目で確認できます。

ただvue-scrollto.jsに書いた方が
まとまって分かりやすいと思います?

nuxt.config.js
 modules: [
   ['vue-scrollto/nuxt', { duration: 700 }],
 ],

step4: テンプレートでページ内リンクを作成

【sample.vue】
・nuxt-link toの中にv-scroll-toを入れる
 toが2個あって変な感じがしますが
 どちらも必要なので削らないように✏️
・リンク先を''で囲む

この書き方は
vue-scrollto公式の
Usage項目で確認できます。

sample.vue
<template>
 <div class="page">
   <nuxt-link
     v-scroll-to="'#anchor'"
     to
   >
     下にとぶ
   </nuxt-link>
   <div id="anchor">
     とべた!
   </div>
 </div>
</template>

これで完成です??

ローカルver

一応ローカルの書き方も。
vue-scrollto.jsをまるごとコピペで⭕️
オプションもdata内に書けば適応されます。

sample.vue
<template>
 <div class="page">
   <nuxt-link
     v-scroll-to="'#anchor'"
     to
   >
     下にとぶ
   </nuxt-link>
   <div id="anchor">
     とべた!
   </div>
 </div>
</template>

<script>
import Vue from 'vue'
import VueScrollTo from 'vue-scrollto'

Vue.use(VueScrollTo, {
 duration: 700,
 easing: [0, 0, 0.1, 1],
 offset: -100,
})

export default {
 data () {
   return {
     options: {
       el: '#anchor',
       onDone: (el) => console.log(el)
     }
   }
 }
}
</script>

<style lang="scss" scoped>
 #anchor {
   margin-top: 1000px;
 }
</style>

今まで週3で投稿していましたが
来週から月水の2回に変更致します?
自社サービスに手をつけているためです。
お楽しみに?

落ち着いたらまた週3になるかも?

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

Vuetifyでボトムナビゲーション上にフローティングボタンをつける

はじめに

Vuetify を使うと優れた UI をとても簡単に作れますが、少し特殊なことをやろうとするとつまづくことが多いです。
本記事では、モバイルアプリでよく見かける、ボトムナビゲーションからはみ出たアイツ をつける方法を記載します。

アイツ:point_down:
btmnav4.png

アイツの一般的な名称は、多分「オーバーラップ FAB」なんですが。

出来上がるもの

003.gif

開発環境

  • Windows10
  • Node.js 12.13.1
  • Atom 1.42.0
  • vue/cli 4.1.1
  • Vue 2.6.1
  • Vuetify 2.0.3

アイコンをインストールする

とりあえずアイコンを用意しましょう。今回は、Fontawesome を使います。
環境に合わせてご自由にどうぞ。

npm install @fortawesome/fontawesome-free -D
main.js
import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';
import '@fortawesome/fontawesome-free/css/all.css' /* この行を追加する */

ボトムナビゲーションを追加する

v-app ディレクティブの後ろにコードを追加します。

App.vue
<template>
  <v-app>

    <!-- 省略 -->

    <!-- ここからボトムナビゲーション -->

    <v-bottom-navigation
      dark
      grow
    >
      <v-btn>
        <span>Home</span>
        <v-icon>fas fa-home</v-icon>
      </v-btn>
      <v-btn>
        <span>Edit</span>
        <v-icon>fas fa-edit</v-icon>
      </v-btn>
    </v-bottom-navigation>

    <!-- ここまでボトムナビゲーション -->

  </v-app>
</template>

ボトムナビゲーションが表示されました!
btmnav1.png

フローティングボタンを用意する

Vuetify では v-speed-dial ディレクティブを使うと、子ボタンを内包したフローティングボタンが使えます。

App.vue
<template>
  <v-app>

    <!-- 省略 -->

    <!-- ここからフローティングボタン -->

    <v-layout justify-center row wrap>
      <v-speed-dial
        v-model="fab"
        >
        <template v-slot:activator>
          <v-btn
            color="red darken-2"
            dark
            fab
          >
            <v-icon v-if="fab">mdi-close</v-icon>
            <v-icon v-else>mdi-plus</v-icon>
          </v-btn>
        </template>
        <v-btn
          fab
          dark
          small
          color="orange"
        >
          <v-icon>mdi-note</v-icon>
        </v-btn>
        <v-btn
          fab
          dark
          small
          color="red"
        >
          <v-icon>mdi-camera</v-icon>
        </v-btn>
        <v-btn
          fab
          dark
          small
          color="indigo"
        >
          <v-icon>mdi-file</v-icon>
        </v-btn>
      </v-speed-dial>
    </v-layout>

    <!-- ここまでフローティングボタン -->

    <!-- 省略 -->

<script>

export default {
  name: 'App',

  components: {
  },

  data: () => ({
    fab: false, /* フローティングボタン のために追加 */
  }),
};
</script>

001.gif

あとはフローティングボタンを、ボトムナビゲーションに入れたら完成ですね:star:

完成、そんなことはなかった

すみません、どうやら簡単にはできないようです。
ボトムナビゲーションにフローティングボタンを入れると、とても残念な結果になります。

002.gif

一応ソースも置いておきます。

App.vue
<template>
  <v-app>

    <!-- 省略 -->

  <v-bottom-navigation
      dark
      grow
    >
      <v-btn>
        <span>Home</span>
        <v-icon>fas fa-home</v-icon>
      </v-btn>

      <!-- ここからフローティングボタン -->

      <v-layout justify-center>
        <v-speed-dial
          v-model="fab"
          >
          <template v-slot:activator>
            <v-btn
              color="red darken-2"
              dark
              fab
            >
              <v-icon v-if="fab">mdi-close</v-icon>
              <v-icon v-else>mdi-plus</v-icon>
            </v-btn>
          </template>
          <v-btn
            fab
            dark
            small
            color="orange"
          >
            <v-icon>mdi-note</v-icon>
          </v-btn>
          <v-btn
            fab
            dark
            small
            color="red"
          >
            <v-icon>mdi-camera</v-icon>
          </v-btn>
          <v-btn
            fab
            dark
            small
            color="indigo"
          >
            <v-icon>mdi-file</v-icon>
          </v-btn>
        </v-speed-dial>
      </v-layout>

      <!-- ここまでフローティングボタン -->

      <v-btn>
        <span>Edit</span>
        <v-icon>fas fa-edit</v-icon>
      </v-btn>
    </v-bottom-navigation>
  </v-app>
</template>

ボタンのスタイルを !important で無理やり書き換えれば多少の抵抗はできそうですが、時間のムダです。
もちろん僕はやりました:innocent:
やめておいた方が吉です:middle_finger:

ボトムナビゲーションとフローティングボタンは分離する

ボトムナビゲーション内にフローティングボタンは置けません:skull:

ボトムナビゲーションは画面の下部に固定されるので、ついでにフローティングボタンもボトムナビゲーションの中に含めたくなりますが、それではうまくいきません。
そこで、v-footer ディレクティブを用意し、その中に、フローティングボタンとボトムナビゲーションを入れます。

newdir.jpg

...というわけで、v-footer ディレクティブの中に、フローティングボタンとボトムナビゲーションを入れます。
また、v-footer には fixed プロパティを付与し、画面下部に固定します。

App.vue
<template>
  <v-app>

    <!-- 省略 -->

  <v-footer
     padless
     fixed
      >
      <v-row>
        <v-col
          cols="12"
          class="pa-0"
          >
          <v-layout justify-center>
            <v-speed-dial
              v-model="fab"
              >
              <template v-slot:activator>
                <v-btn
                  color="red darken-2"
                  dark
                  fab
                  class="float"
                >
                  <v-icon v-if="fab">mdi-close</v-icon>
                  <v-icon v-else>mdi-plus</v-icon>
                </v-btn>
              </template>
              <v-btn
                fab
                dark
                small
                color="orange"
              >
                <v-icon>mdi-note</v-icon>
              </v-btn>
              <v-btn
                fab
                dark
                small
                color="red"
              >
                <v-icon>mdi-camera</v-icon>
              </v-btn>
              <v-btn
                fab
                dark
                small
                color="indigo"
              >
                <v-icon>mdi-file</v-icon>
              </v-btn>
            </v-speed-dial>
          </v-layout>
        </v-col>
        <v-col
         cols="12"
         class="pa-0"
          >
          <v-bottom-navigation
            dark
            grow
          >
            <v-btn>
              <span>Home</span>
              <v-icon>fas fa-home</v-icon>
            </v-btn>
            <v-btn>
              <span>Edit</span>
              <v-icon>fas fa-edit</v-icon>
            </v-btn>
          </v-bottom-navigation>
        </v-col>
      </v-row>
    </v-footer>
  </v-app>
</template>

ようやくまともに表示できました:relieved:
btmnav2.png

仕上げ

フローティングボタンをボトムナビゲーションに重ねます。
まず、フローティングボタンを配置している row の高さを 0 にします。

App.vue
<template>
  <v-app>

    <!-- 省略 -->

    <v-footer
     padless
     fixed
      >
      <v-row>
        <v-col
          cols="12"
          class="pa-0"
          style="height: 0px;"  <!-- 高さを 0 にする -->
          >
          <!-- 省略 -->
        </v-col>
      </v-row>
    </v-footer>
  </v-app>
</template>

完全に重なりましたね。
btmnav3.png

次に、フローティングボタンを上方向にズラすために、スタイルを適用します。

App.vue
<template>
  <v-app>

    <!-- 省略 -->

              <template v-slot:activator>
                <v-btn
                  color="red darken-2"
                  dark
                  fab
                  class="float"  <!-- 位置調整用のスタイルを適用 -->
                >
                  <v-icon v-if="fab">mdi-close</v-icon>
                  <v-icon v-else>mdi-plus</v-icon>
                </v-btn>
              </template>

    <!-- 省略 --> 

  </v-app>
</template>

<script>
export default {
  name: 'App',
  components: {
  },
  data: () => ({
    fab: false,
  }),
};
</script>

<style>
.float {
  -webkit-transform: translateY(-20%); /* フローティングボタンを上部にズラす */
  transform: translateY(-20%);
}
</style>

ようやく狙った位置に置くことができました:smile:
btmnav4.png

まとめ

Vuetify は色々と捗り過ぎて素晴らしいですが、単純にやり過ぎるとドツボにハマります。
やはりどのようなフレームワークを使おうが CSS の基本的な知識(特にFlexbox)は大事ですね。

あと、もっと楽な方法あったらコメントいただけると嬉しいです!

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

Vue.jsとVuexで編集formのdateにstateをコピーして同期させずに使いたい

参考
[Vue] Storeのstateをコンポーネントのローカルstateにコピーする
How can I clone data from Vuex state to local data?

結論

this.formData = JSON.parse(JSON.stringify(this.stateData))

問題提起

Vue.jsとVuexを使っていて編集formを作っている時に
Vuexのstateから取り出したデータをcomponentのdataオプションに渡して
dataをv-modelでbindして編集してみた。

すると

dataのオブジェクトをbindしたはずが
stateのオブジェクトも一緒に同期して編集されてしまった。

…後で追記する。

  • 1つずつ代入していたエピソードとコード
  • 実際の利用例
  • 解説
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PlayCanvasでVueJS+OnsenUI環境作ってみた

PlayCanvasでVueJS+OnsenUI環境作ってみた

【ハンズオン】Vue.jsをPlayCanvasの中で使って3DモデルビュワーなWebページを作る
で公式(?)のPlayCanvasでのVueJS環境構築を教えていただいたので、VueJS+OnsenUI環境もできるのではないかと思ってやってみました。


全体

image.png

こんな感じでRootにindex.jsを追加して、属性にindex.htmlとOnsenUIで必要なCSS2つセットしてます。

index.js
/*jshint esversion: 6, asi: true, laxbreak: true*/

const Index = pc.createScript('index');

Index.attributes.add("VueHtml", {type:"asset", assetType:"html"}); // 登録したhtmlを取得
Index.attributes.add("OnsenUICss", {type:"asset", assetType:"css", array: true});

// initialize code called once per entity
Index.prototype.initialize = function() {
//    var self = this; // this書き換え
    const wrapper = document.createElement("div"); // div作成
    wrapper.classList.add("wrapper"); // 作成したdivにwrapperというclass名を指定
    wrapper.innerHTML = this.VueHtml._resources[0]; // 事前に登録していたhtmlをwrapperに流し込み
    document.body.appendChild(wrapper); // bodyにwrapperを追加

    // CSS
    const cssElement = document.createElement("style");
    this.OnsenUICss.forEach((css) => {
        cssElement.innerHTML = css._resources[0];
        document.head.appendChild(cssElement);
    });

    // VueJS
    const app = new Vue({ // Vue呼び出し
        el: '#app', // id名がappの要素を参照
        template: '#main',
        data() { // 使用するdataを登録
            return({
            });
        },
        methods: { // イベントハンドラ作成
        }
    });

    // canvasを取得
    const canvas = document.getElementsByTagName("canvas")[0]; // canvasを取得
    canvas.classList.add("pcCanvas"); // canvasにclass名を指定

    // canvasを移動
    const canvasInsert = document.getElementById('canvas_insert');
    canvasInsert.appendChild(canvas);
};

// update code called every frame
Index.prototype.update = function(dt) {

};

// swap method called for script hot-reloading
// inherit your script state here
// Index.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/
index.html
<template id="main">
    <v-ons-page>
        <!-- PlayCanvasのcanvas移動先 -->
        <div id="canvas_insert"></div>

        <p style="text-align: center">
            <v-ons-button @click="$ons.notification.alert('Hello World!')">
                Click me!
            </v-ons-button>
        </p>
    </v-ons-page>
</template>

<div id="app"></div>

onsenui.cssはOnesnUI用CSS
onsen-css-components.min.cssはOnesnUIコンポーネント用CSS

をコピペして作成

ExternalScriptには
- https://cdn.jsdelivr.net/npm/vue
- https://unpkg.com/onsenui/js/onsenui.min.js
- https://unpkg.com/vue-onsenui@2.6.2/dist/vue-onsenui.js

を指定したら

image.png
こんな感じでVueJS+OnsenUIで動きました

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

【Nuxt.js】 asyncDataとfetchって結局どう使うの?

TL;DR.

  • asyncDatafetchはコンポーネントがロードされる度に呼び出され、外部からデータを取得する際に使用する
  • acyncDataは外部から取得したデータをページコンポーネントのみで使用する場合に使用する
  • fetchは取得したデータをVuexのstoreに格納して使用したい場合に使用する

そもそもacyncDataとfetchって何?

  • acyncDataとfetchはページコンポーネントの初期化前に呼び出される関数のこと。
  • SSR(サーバーサイドレンダリング)、ページ遷移前にも呼び出される。
  • 第1引数にcontext(オブジェクト)を取るので、クエリパラメータなどの値にアクセスして処理を行うことができる。
  • 一方で、コンポーネントのインスタンスが作成される前に処理が実行されてしまうため、thisでコンポーネントのインスタンスにアクセスすることができない。

aycncDataの使用法

acyncDataは外部からデータを取得し、ページコンポーネントへ直接セットすることを目的として使用される。asyncDataによって返される値はコンポーネントのテンプレートからアクセスすることで使用できる。

<template>
  <div>{{ hoge }}</div>
</template>

<script>
export default {
  async asyncData({ app }) {
    const doc = await app.$firebase.firestore().collection('hoge').get()
    return { hoge: doc.data().hoge }
  }
}
</script>

fetchの使用法

fetchは外部から取得したデータをVuexのstoreに格納して使用することを目的として使用される。acyncDataとは違ってコンポーネントに値を直接セットすることができない。

<template>
  <div>{{ $store.state.hige }}</div>
</template>

<script>
export default {
  async fetch({ app, store }) {
    const doc = await app.$firebase.firestore().collection('hige').get()
    store.commit('setHige', doc.data().hige)
  }
}
</script>

結局どう使うの?

  • SSRとCSRのライフサイクルはpluginmiddlewarevalidate→asyncData→fetchbeforeCreateの順で処理が実行されているので、タイミング的にはどちらを使用しても影響範囲は変わらないように思える。

  • asyncDataの場合、そのページで直接apiを叩いてデータを取得することを目的としているため、Vuexを使用して共通化している関数などを使用しない。したがって、storeに格納しないデータ(アカウントデータやテーマなどの情報はlocalStrageやsessionStrageかから取得することを想定)を使用してデータ取得を行いたい場合に使用するイメージ。また、asyncData内でstoreを使用することはできるが、gettersを動かしてページにデータを持ってくる必要がある。

  • fetchの場合、Vuexのmutationsを直接叩いてstoreに値をセットして、ページから直接storeを参照するために使用する。したがって、取得したデータの加工や表示方法をstoreに入れる前に全て行う必要がある。つまり、一旦storeに入れてしまったデータを編集して表示することが難しい。取得したデータを完全にページで成形して使用する場合(マスターデータや表示のみで使用する一覧系データなど)に使用するイメージ。また、アプリケーションないで使用しているデータや処理方法がVuexに大きく依存している場合、かなり有効になる。

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

Vue.jsのドキュメントを初心者なりに読んで解釈してみた②

前書き

前回は「Vue インスタンス」まで読み進めました。
いよいよ今回は Vue.js の基本になるであろうテンプレート構文を読んでいきます。

途中、「ドキュメントのサンプルコードだけじゃ分からん……」となった箇所で、自分なりに理解するためにコードを書いています。いうまでもなくガチ初心者が書いたコードなので、エレガントじゃないとか色々あると思いますが、そこは目を瞑ってください。

ただ、間違った解釈をしていたり、不足していることがあったときは、優しくマサカリをコメント欄に置いていっていただけますと幸いです。

テンプレート構文

ドキュメントの順序通り書き下したいところですが、「ディレクティブって何ぞや?」と乱舞する片仮名が気になって仕方ないので、簡単な説明の部分だけ先にさらってしまいましょう。

ディレクティブ(雑な要約)

ディレクティブとは、v- から始まる特別な属性です。ディレクティブの仕事は、属性値の式が変化したときに、リアクティブに副作用を DOM に適用することです。
「はじめに」の条件分岐とループのチュートリアルが分かりやすい例ですね。v-if ディレクティブが、seen という式の真偽値をもとに <p> 要素を削除したり挿入していました。

ディレクティブについては後でまた書き下します。
用語が分かったところで、改めてドキュメントの上から順に読んでいきましょう。

展開

テキスト
もっとも基本的な形は Mustache構文 という、{{}} を使ったテキスト展開です。宣言的レンダリングの最初のチュートリアルに出てきましたね。
チュートリアルで体験した通り、対応するオブジェクトのプロパティの値に置き換わります。プロパティの値を変更すると、それに応じて更新されます。

v-once を使うことで、値を変更しても更新を行わず、一度だけ展開することができます。
ただし、v-once が使われていない部分では影響を受けます。

ドキュメントのコードだとピンと来なかったので、実験的にコードを書いてみました。

index.html
<div id="app_A" v-once>
    {{ message }}
</div>
<div id="app_B">
    {{ message }}
</div>

{{message}} を突っ込んだ2つの div があります。div#app_A には v-once を指定し、div#app_B には何も指定していません。
両方に message の内容が表示されるようにコードを書きます。

index.js
var msg = {
    message: 'Hello Vue!'
}

var app_A = new Vue({
    el: '#app_A',
    data: msg
})

var app_B = new Vue({
    el: '#app_B',
    data: msg
})
結果
Hello Vue!
Hello Vue!

無事に同じテキストが表示されました!
では、コンソールで app_A.message の内容を test に変えてみましょう。

結果
Hello Vue!
test

div#app_A には v-once が指定されているので、message の内容が変更されても「Hello Vue!」が表示されたままです。一方、div#app_Bmessage の更新に応じて表示が変わりました。
念のためコンソールで app_A.message の中身を見てましょう。 test になっていますね。

ドキュメントで述べられている「同じノードのあらゆる他のバインディングが影響を受ける」というのは、こういうことだと思います。多分。

生のHTML
{{}} に入れたデータは、HTMLではなくプレーンなテキストとして扱われます。

ドキュメントではHTMLコードと結果しか書いておらず、肝心の rawHtml に何が入っているのか一見わかりづらいですが、<span style="color:red">This should be red.</span> という文字列が入っています。
前述の通り、{{}} で展開されたデータはプレーンなテキストとして扱われるので、文字列がそのまま出ています。
v-html ディレクティブを使用したほうは、プレーンなHTMLテキストとして解釈されるので、「This should be red.」という赤字が表示されます。

Vue は、文字列ベースのテンプレートエンジンではないので~の部分はよくわかりませんでした。取り敢えず今は、みだりに使わないようにするという程度の理解で進めます。ちゃんと説明できるようになったら加筆します。

属性
{{}} は、HTML属性の内部で使えません。

NG例
<!-- dynamicId にIDとして指定したい文字列を入れた想定 -->
<div id="{{dynamicId}}"></div>
<div id={{dynamicId}}></div>

<!-- dynamicId に id="hoge" みたいな文字列を入れた想定 -->
<div {{dynamicId}}></div>

代わりに、v-bind を使います。ドキュメントに書いてありますが、比較のため再掲してみましょう。

OK
<div v-bind:id="dynamicId"></div>

v-bind: と書いた後に、使いたいHTML属性を書いて、データを指定する!って感じで書けば良さそうですね。

HTML属性が存在していることを truefalse といった真偽値属性で示す場合、v-bind の挙動は少しだけ変わります。
ドキュメントのコードを元に実験してみましょう。まずは true の場合です。

index.html
<button id="app" v-bind:disabled="isButtonDisabled">Button</button>
index.js
var app = new Vue({
    el: '#app',
    data: {
        isButtonDisabled: true
    }
})
描画結果
<button id="app" disabled="disabled">Button</button>

次に、isButtonDisabled の値を false にしてみます。

index.js
var app = new Vue({
    el: '#app',
    data: {
        isButtonDisabled: false
    }
})
描画結果
<button id="app">Button</button>

disabled 属性がまるごと出力されませんでしたね。
このように、真偽値属性が falsenullundefined の場合は、HTML属性を含まず要素が描画されます。

JavaScript 式の使用
Vue.js は、データバインディング(=データと対象を結び付けて反映する)内部で、JavaScript の式を使うことができます。
ドキュメントのコードを使って、確かめていきます。

index.html
<div id="app">
    <p>{{ number + 1 }}</p>
    <p>{{ ok ? 'YES' : 'NO' }}</p>
    <p>{{ message.split('').reverse().join('') }}</p>
    <div v-bind:id="'list-' + id">sample</div>
</div>
index.js
var app = new Vue({
    el: '#app',
    data: {
        number: 1,
        ok: false,
        message: 'ABC',
        id: 'a'
    }
})
結果
<p>2</p>
<p>NO</p>
<p>CBA</p>
<div id="list-a">sample</div>

HTML 側で書いた JavaScript の式が、結果に反映されました。

注意したいのは、どんな式でも動作するわけではないことです。
ドキュメントで挙げられているような「変数の宣言」「if文」といった単一の式ではないもの(要はその一文だけでは完結しない式、という認識でいいのかな……)は対象外です。

ディレクティブ

先に書いた通り、v- から始まる特別な属性を指します。v-for を除くディレクティブ属性値は、単一の JavaScript 式を期待し、その式が変化したとき、リアクティブに副作用を DOM に適用します。

引数
ディレクティブ名の後にコロンを表記して、引数をとるディレクティブもあります。
例えば、 v-bind は、HTML 属性を引数として、リアクティブにその値を更新します。v-on の場合は、click などの受け取りたいイベント名を引数として取ります。

基本的な書き方
v-ディレクティブ名:引数="値"

動的引数
Vue.js バージョン2.6.0から、[] で囲むことで、JavaScript 式をディレクティブの引数に使うことができるようになりました。

動的引数を使う書き方
v-ディレクティブ名:[値1]="値2"

ただし、いくつかの制約があります。

  1. の制約
    要は null と string(文字列)しか使えない、ということだと思います。真偽値とか入ってても、ねえ……ってことでしょう。多分。
    null は明示的にバインディングを削除するのにつかわれる、とドキュメントにありますが、例となるコードが無いのでちょっと理解ができませんでした。

  2. の制約
    構文上の制約があります。例えばスペースとか引用符なんかは、HTML 属性としては不正な文字なので、使ってはいけません。それらを含まない式を使うか、置き換えが必要です。
    また、HTML ファイルに直接書く場合、ブラウザが属性名を強制的に小文字にしてしまいます。小文字変換後のプロパティがあれば動きますが、後々管理に苦労することが容易に想像つくので、キャメルケースなど大文字を混ぜるようなことは原則として避けましょう(というか、禁止という認識で良い問題ないと思います)。

ダメな例
<!-- スペースや引用符が含まれている -->
<a v-bind:['foo' + bar]="value"> ... </a>

<!-- この someAttr は someattr に変換されるので、someattr プロパティが無いと動かない -->
<a v-bind:[someAttr]="value"> ... </a>

修飾子
修飾子(Modifier といいます)は、. で表記された特別な接尾語です。ディレクティブ v- を、特別な方法でバインドすることを示します。
サンプルコードがよくわからないので、v-onv-model の説明を読むときに改めて復習しようと思います。

省略記法

v- 接頭辞のうち、最もよく使われるディレクティブである v-bindv-on には特別な省略記法が提供されています。

v-bindの省略記法
<!-- 完全な構文 -->
<a v-bind:href="url"> ... </a>

<!-- 省略記法 -->
<a :href="url"> ... </a>

<!-- 動的引数の省略記法 (2.6.0 以降) -->
<a :[key]="url"> ... </a>
v-onの省略記法
<!-- 完全な構文 -->
<a v-on:click="doSomething"> ... </a>

<!-- 省略記法 -->
<a @click="doSomething"> ... </a>

<!-- 動的引数の省略記法 (2.6.0 以降) -->
<a @[event]="doSomething"> ... </a>

修飾子がつくときはどうするんだろう?と思ったのですが、ちょうど先程の v-on の修飾子のページを見ていた時に見つけました。普通に後ろにくっつけて良さそうです。

<button @click.ctrl="onClick">A</button>

次回予告

基本的な記法が分かって楽しくなってきた半面、ドキュメントの咀嚼が難しくなってきました……。
順番通り、次は算出プロパティを頑張って読んでいこうと思います。

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

WordPressの固定ページをでVue.jsを使うには

お断り:今回は新規構築サイトの「固定ページ」でVue.jsを使用した際のメモです。
構築済みサイトの場合、既存の記事に悪影響がないことは保障しかねます。

なぜやろうと思ったのか?

jQueryしか使えない筆者が、Vue.jsの学習を兼ねて簡単なWebアプリを構築しました。
テーマを利用してデザインの手間は省くため WordPressにVue.jsを組み込むに至りました。

初学者なので間違ったことをお伝えしていたらご指導お願いします?

必要な手順

WordPressで使うといっても、普通のWebページに導入するのとほとんど変わりません。

①サーバー上にNode.jsを導入する

レンタルサーバーでなければ特に苦労なく導入できると思います。
参考記事:Samurai Blog様

私の場合レンタルサーバー(ColorfulBox)をお借りしていたので少し苦労しました。
レンタルサーバーの始め方 様の情報通り作業すれば導入できました。

②WordPressでVue.jsを読み込む

公式サイトからダウンロードし、サーバーに配置します。

詳細な方法は「WordPress javascript 読み込み」などで検索すればたくさん出てくるので、
ニーズに合った方法を選択してください。

ここまでで準備は完了です。

③HTMLをコーディング

普通にHTMLを記述すると自動で成形されてしまうので、
カスタムHTMLブロックを利用するとストレスフリーです。

固定ページ(カスタムHTML)
<div id="app">
  {{ message }}
</div>

④スクリプトをコーディング

私の場合、固定ページごとに個別のスクリプトを仕込みたかったので「Scripts n Styles」というプラグインで対応しました。

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

このプラグインを利用する場合、スクリプトの出力先を「head」か「body」で選べるのですが「body」に出力してください。
「head」だと動きませんでした。
body推奨.png

動作確認

Scripts n StylesはUpdate Scriptsを押下し、
固定ページを公開してアクセスしましょう。
動作確認.png

{{ message }} ではなく Hello Vue! と表示されていますね!

お疲れ様でした!!

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

WordPressでVue.jsを使うには

お断り:今回は新規構築サイトの「固定ページ」でVue.jsを使用した際のメモです。
構築済みサイトの場合、既存の記事に悪影響がないことは保障しかねます。

なぜやろうと思ったのか?

jQueryしか使えない筆者が、Vue.jsの学習を兼ねて簡単なWebアプリを構築しました。
テーマを利用してデザインの手間は省くため WordPressにVue.jsを組み込むに至りました。

初学者なので間違ったことをお伝えしていたらご指導お願いします?

必要な手順

WordPressで使うといっても、普通のWebページに導入するのとほとんど変わりません。

ただ、Googleで軽く調べても「WordPressでVue.jsを使う」みたいな記事が出てこず、
(もしかして使えない…?)とか思ってしまったので情報を残しておきたいと思いました。

①サーバー上にNode.jsを導入する

レンタルサーバーでなければ特に苦労なく導入できると思います。
参考記事:Samurai Blog様

私の場合レンタルサーバー(ColorfulBox)をお借りしていたので少し苦労しました。
レンタルサーバーの始め方 様の情報通り作業すれば導入できました。

②WordPressでVue.jsを読み込む

公式サイトからダウンロードし、サーバーに配置します。

詳細な方法は「WordPress javascript 読み込み」などで検索すればたくさん出てくるので、
ニーズに合った方法を選択してください。

ここまでで準備は完了です。

③固定ページの作成(HTMLをコーディング)

普通にHTMLを記述すると自動で成形されてしまうので、
カスタムHTMLブロックを利用するとストレスフリーです。

固定ページ(カスタムHTML)
<div id="app">
  {{ message }}
</div>

④固定ページの作成(スクリプトをコーディング)

私の場合、固定ページごとに個別のスクリプトを仕込みたかったので「Scripts n Styles」というプラグインで対応しました。

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

このプラグインを利用する場合、スクリプトの出力先を「head」か「body」で選べるのですが「body」に出力してください。
「head」だと動きませんでした。
body推奨.png

動作確認

Scripts n StylesはUpdate Scriptsを押下し、
固定ページを公開してアクセスしましょう。
動作確認.png

{{ message }} ではなく Hello Vue! と表示されていますね!

お疲れ様でした!!

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