20200119のvue.jsに関する記事は14件です。

Vue.js基礎

データバインディング

データと描画を同期する仕組み。

v:bindについて

・dataオプションに指定した、データをデータバインディングする。

        <p id='app'>
      <input type=“text” v-bind = “message”>
        </p>
 <script>

        const app = new Vue({
            el: '#app',//htmlのappの中でvue.jsを適用する*
            data: {
                toggle: true
                  message: "Hello Vue.js!" 
            }
        });
    </script>

とすることにより、テキストボックスの中に[Hello]と表示される
c7c1f0e309f0ef3e0fa8d3491a0f0ebf.png

v-ifについて

・要素の表示/非表示を切り替える

 <div id=“app”>
        <p v-if=“toggle”>
            dousita
        </p>
    </div>
 <script>

        const app = new Vue({
            el: '#app',//htmlのappの中でvue.jsを適用する*
            data: {
                toggle: true
            }
        });
    </script>

toggleがfalseになっていたら、dousitaが表示されず、
toggleがtrueになっていたら、dousitaが表示される。

v-if と v-showの違い

v-if:falseでは、DOMレベルで要素が削除されてしまうが、
v-showでは、検証ツールで見てみると、

<div id="app"><p style="display: none;">
            dousita
             </p>
</div>

display:noneという形で、DOMからは要素は削除されない。
CSSのディスプレイプロパティを利用して、表示/非表示を切り替えている

v-forについて

・オブジェクトの繰り返しを、v-forディレクティブで行う方法

  <div id=“app”>
        <ol>
            <li v-for=“color in colors>{{ color }}</li>
        </ol>
    </div>
<li v-for=“color in colors>{{ color }}</li>

colorは任意の値でも良いが、単数形にしておくとコードの可読性が上がる。colorsの部分には、表示に使う配列を指定する。

<script>

        const app = new Vue({
            el: '#app',//htmlのappの中でvue.jsを適用する
            data: {
                colors: ['red','green','blue']
          }
        });

</script>

コンポーネントについて

  • 名前付きの再利用可能なVueインスタンス。
  • UIパーツを作成することができる。
  • 何回も使いまわせる。
<div id=“app”>
        <hello-component></hello-component>
    </div>

    <script>
        Vue.component('hello-component',{
           template: '<p>Hello</p>'
        })
        const app = new Vue({
            el: '#app',//htmlのappの中でvue.jsを適用する
            data: {
                message: 'Hello!!!'
           }

        });
    </script>

2722603c6bca95f9e25149b99e53dbc0.png

と表示される。

<script>
Vue.component('コンポーネントの名前',{
           template: 'htmlに表示させたい内容'
        })
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails+WebpackerにVue.jsとReactの両方を入れる

環境:Rails 6.0、Webpacker 4.2、Vue.js 2.6、React 16.12

Webpackerを使ってVue.jsとReactの両方を動かすことに成功したので、メモしておきます。

コピー元の作成

まず、設定ファイルのコピー元とするだけのアプリケーションを作成します。すでにVueを入れたアプリケーションがある場合は、Reactで作ります。

% rails new reactapp --webpack=react

Reactを入れたアプリケーションがある場合は、Vueを指定します。

% rails new vueapp --webpack=vue

yarn add

Vueで作ってあるアプリケーションには、Reactのモジュールをインストールします。prop-typesはWebpackerがデフォルトで入れるものですが、必須ではありません。

% yarn add @babel/preset-react babel-plugin-transform-react-remove-prop-types prop-types react react-dom

Reactで作ってある場合は、Vueのモジュールをインストールします。

% yarn add vue vue-loader vue-template-compiler vue-turbolinks

babel.config.js

ルートにあるbabel.config.jsは、Reactで生成したものを使います。つまり、すでにReactならそのままにし、VueならReact用のbabel.config.jsを上書きします。

config/webpacker.yml

config/webpacker.ymlは、元からあるものを使い、Reactを加える場合は.jsxを、Vueを加える場合は.vueを追加します。

config/webpacker.yml
  extensions:
    - .vue
    - .jsx

config/webpacker/

ReactにVueを加える場合は、Vueで生成したものからconfig/webpacker/loaders/vue.jsをコピーします。

また、config/webpacker/environment.jsはVueのもので上書きします。

両方動かしてみる

次のような感じで両方を動かすapplication.jsを書いて動けば成功です。

app/javascript/packs/application.js
require("@rails/ujs").start();
require("turbolinks").start();

import React from 'react';
import ReactDOM from 'react-dom';

import Vue from 'vue';
import TurbolinksAdapter from 'vue-turbolinks';

import VueApp from '../vueapp';
import ReactApp from '../reactapp';

Vue.use(TurbolinksAdapter);

document.addEventListener('turbolinks:load', () => {
  if($('#vue-app').length) {
    new Vue(VueApp).$mount('#vue-app');
  }

  if($('#react-app').length) {
    ReactDOM.render(React.createElement(ReactApp), $('#react-app')[0]);
  }
});

実際にこんなアプリケーションを作ることはないと思いますが、現実的な使い方として考えられるのは、Railsアプリケーションの中でVueを使う部分とReactを使う部分を分けるケースです。その場合は、app/javascript/packsの下にVue用とReact用のxxx.jsを作り、レイアウトテンプレートを複数作ってjavascript_pack_tagを切り替える、ということになるでしょう。

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

【Vue.JS】 WebPackを使わずにコンポーネントで遊ぶ

対象読者

  • 1. 環境構築はやりたくないけど、すぐにVueで遊んでみたい方
    • .vueファイルを使用する場合、WebPackサーバーなどの環境構築が必要となる
  • 2. 自分のPC上でとりあえず遊んでみたい方
    • ※ サイトとして公開する場合は、別途環境構築が必要

サンプルアプリ

  • クリックすると赤色→緑色に変化するコンポーネントを作る

手法

  • .vueファイルを使わずに、全て.jsファイルで定義する
  • import文 , export文を使わずにコンポーネントを使用する
    • 通常のJavaScriptでimport文を使用するとエラーとなるため

作り方

フォルダ構成

  • 空のフォルダを作成し、その中に以下の4ファイルを作成する
    • フォルダ名は任意。ここでは sample_folder とした
フォルダ構成
sample_folder
    ┝ application.html
    ┝ application.js
    ┝ parent_vue.js   ← 自作のコンポーネントを記述する
    ┝ vue.js          ← CDNをダウンロードする(後述)

ファイル生成方法

  • 下記のソースコードをファイルごとに丸コピすれば動作します
  • 解説を見たい方
    • 各ソースコードの下に解説がございます

1. HTMLファイル

application.html
<!DOCTYPE html>
<html lang="js">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>SampleApp</title>
</head>
<body>
  <div id="app">
    <!-- ② このdiv内で自作コンポーネントを呼び出している -->
    <parent-component></parent-component>
  </div>
</body>
  <!-- ① 同一フォルダ内のファイルをインポートする。-->
  <script src="vue.js"></script>
  <script src="parent_vue.js"></script>
  <script src="application.js"></script>
</html>
  • <script src="○○.js"></script>の部分

    • この部分で同じフォルダ内の.jsファイルを全て読み込む
  • ② 自作したコンポーネントを呼び出すために、<div id="app"></div>タグを作成する

    • このdivの外では、vueコンポーネントを使用できないことに注意
    • id名は何でも良いが、後述するapplication.js内で定義するid名と同名とする
    • <parent-component></parent-component>が自作したコンポーネントタグである
      • 上記のタグ名は何でも良いが、後述するparent_vue.js内で定義するコンポーネント名と同名でなければならない

2. JSファイル(コンポーネント呼び出し用)

application.js
var parent = new Vue({
    el: "#app",
    // application.htmlで定義したid名と同一にする
});
  • このファイルでVueインスタンスを生成する
    • el:には、application.htmlで定義したid名を書く

3. JSファイル(コンポーネント定義用)

parent_vue.js
// Vue.component('コンポーネント名', {定義}) の形式とする
Vue.component('parent-component',{
  template: 
  ` <div id="parent_root">
      <div v-bind:style="myStyle" v-on:click="changeColor">
        {{message}}
      </div>
    </div>
  `,
  // component内ではdataは関数として定義しなければならない
  data: function() {
    return {
        message: "Click! Me.",
        myStyle: {
          color: 'red',
          fontSize: '18px',
          "background-color": '#ebb',
          border: 'solid 2px',
        }
    }
  },
  methods: {
    changeColor: function(){
      console.log("checked.");
      this.message ='confirmed'; 
      this.$set(this.myStyle, 'color', 'green'); 
      this.$set(this.myStyle, 'color', 'green'); 
      this.$set(this.myStyle, 'background-color', '#beb'); 
    }
  },
});
  • このファイルでは、自作のコンポーネントを定義する
    • Vue.component('コンポーネント名', {定義})の形式とし、{ }内に記述する
    • コンポーネント名は、application.htmlで使用するコンポーネントタグと同名でなければならない

補足: templateの書き方(規則)

  • template:部分とは
    • htmlに相当する部分。下記2点を守ること
      • 1. template内では、初めと終わりをバッククォーテーションで囲うこと
      • 2. template内では、必ず単一の<div>タグで囲うこと
参考)templateの書き方
template:
  `<div>
    この中に、htmlタグを自由に記述する
  </div>`,

補足: Style(CSSに相当)の書き方

  • 今回の書き方では<style>タグが使用できないため、代わりに2つの手法を紹介する
    • 1. v-bind:styleを使用し、自作のスタイルをdata:内で定義する
      • 本記事ではこの方法を採用。myStyleとして定義している
    • 2. is属性を使用して、styleタグを偽装する
      • template内に一旦別名のタグを作成し、is属性でstyleタグとして機能させる
      • 方法はこちら

4. CDNファイル

  • Vue公式のCDNダウンロードサイトを開き、直接組み込みの章までスクロールする
  • 開発バージョンボタンをクリックし、ファイルをダウンロード
  • ダウンロードしたvue.jsファイルを上記のsample_folder内にコピーすれば準備完了
    • (記事上部のフォルダ構成の章を参照のこと)
    • 簡単!

動作

  • 作成したapplication.htmlをダブルクリックすれば、ページが表示されます

参考

  • Vue.JS入門
    • たにぐちまこと氏の "ともすたチャンネル" (Youtube)
    • 初心者にもわかりやすく説明されているため、オススメです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vuejs 外部css読込

<script>
...
</script>

<style>

@import "./css/style.css"

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

Vue DevtoolsがVue.js not detectedと表示される際の対処法

localhost作業時にて、Vue.js not detectedと表示されたとき

vu.png

を許可する。

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

Flask + Vue.js + GitHub Pages で作ったTCGカードの検索アプリ

sample1.png

Cryspe-Prices

まずはじめに作ったアプリについて紹介します。
アプリのリンク : https://github.com/knakajima3027/cryspe-prices

CryptSpells

ブロックチェーン技術を使ったトレーディングカードゲームです。ブロックチェーン技術により、カードの種類ごとにゲーム内での発行枚数が制限されており、カードに価値が付与されるため、ユーザー間でカードの売買が行われています。

作ったきっかけ

CryptSpellsをプレイする上で特定のカードを入手したい時には、他のユーザーからカードを売ってもらう必要があります。しかし、カードの販売を行えるC to Cサービスが複数あるため、特定のカードをなるべく安く買いたいという場合、各サービスの出品情報を全て調べる必要があります。

アプリの概要

そこで,各カード販売サービスの出品情報を定期的にスクレイピングして、一つのデータベースに情報をまとめることで、各カード販売サイトをそれぞれチェックすることなく、一括でカードの検索を行えるアプリを作りました。

利用技術

利用技術は次の通りです。

サーバーサイド

  • Python
  • Flask
  • SQLAlchemy
  • Beautiful Soup
  • Heroku

フロントエンド

  • Vue.js
  • Vue-cli
  • Vuetify.js

全体構成

architect.png

スクレイピング

スクレイピングには、PythonのBeautiful Soupを利用しました。
「対象としているカード販売サイトのスクレイピングを行い、カード情報を集めてデータベースに入れる」という処理のPythonスクリプトをFlaskのコードと一緒にHerokuに置いておき、herokuの設定で1時間ごとにスクリプトを実行するようにしています。

(参考記事)

APIサーバー

APIサーバーはFlaskで制作して、Herokuにデプロイしました。デプロイの手順、デプロイ後のデータベースの作り方などは、下記の記事を参考にさせていただきました。
APIは、クライアントからパラメータ(「カード名」, 「値段」)を受け取り、それに該当するデータをデータベースから検索して、クライアントに返すというシンプルな設計です。

(参考記事)

Vue.js (vue-cli)

GitHub Pagesで表示するクライアントの部分は、vue-cliを利用して制作しました。Vue.jsを含めて、vue-cliを使うのはほぼ初めてでしたが、下記の記事を一通り読むことで概要を掴み、プロジェクトの初期設定からAjaxでのAPIリクエスト行う所までの流れを把握できました。

(参考記事)

GitHub Pages

vue-cliで作成したページは、簡単にGitHub Pagesにデプロイできます。

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

Flask + Vue.js + GitHub Pages で作ったカード検索アプリ

sample1.png

Cryspe-Prices

まずはじめに作ったアプリについて紹介します。
アプリのリンク : https://github.com/knakajima3027/cryspe-prices

CryptSpells

ブロックチェーン技術を使ったトレーディングカードゲームです。ブロックチェーン技術により、カードの種類ごとにゲーム内での発行枚数が制限されており、カードに価値が付与されるため、ユーザー間でカードの売買が行われています。

作ったきっかけ

CryptSpellsをプレイする上で特定のカードを入手したい時には、他のユーザーからカードを売ってもらう必要があります。しかし、カードの販売を行えるC to Cサービスが複数あるため、特定のカードをなるべく安く買いたいという場合、各サービスの出品情報を全て調べる必要があります。

アプリの概要

そこで,各カード販売サービスの出品情報を定期的にスクレイピングして、一つのデータベースに情報をまとめることで、各カード販売サイトをそれぞれチェックすることなく、一括でカードの検索を行えるアプリを作りました。

利用技術

利用技術は次の通りです。

サーバーサイド

  • Python
  • Flask
  • SQLAlchemy
  • Beautiful Soup
  • Heroku

フロントエンド

  • Vue.js
  • Vue-cli
  • Vuetify.js

全体構成

architect.png

スクレイピング

スクレイピングには、PythonのBeautiful Soupを利用しました。
「対象としているカード販売サイトのスクレイピングを行い、カード情報を集めてデータベースに入れる」という処理のPythonスクリプトをFlaskのコードと一緒にHerokuに置いておき、herokuの設定で1時間ごとにスクリプトを実行するようにしています。

(参考記事)

APIサーバー

APIサーバーはFlaskで制作して、Herokuにデプロイしました。デプロイの手順、デプロイ後のデータベースの作り方などは、下記の記事を参考にさせていただきました。
APIは、クライアントからパラメータ(「カード名」, 「値段」)を受け取り、それに該当するデータをデータベースから検索して、クライアントに返すというシンプルな設計です。

(参考記事)

Vue.js (vue-cli)

GitHub Pagesで表示するクライアントの部分は、vue-cliを利用して制作しました。Vue.jsを含めて、vue-cliを使うのはほぼ初めてでしたが、下記の記事を一通り読むことで概要を掴み、プロジェクトの初期設定からAjaxでのAPIリクエスト行う所までの流れを把握できました。

(参考記事)

GitHub Pages

vue-cliで作成したページは、簡単にGitHub Pagesにデプロイできます。

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

Vue TypeScript バリデーションチェック

Vuetifyのrulesを使う。

Vuetifyのrulesを使うことで比較的簡単に実装できます。
なので今回はvuetifyrulesを使います。

前にはてなブログでvuetifyについて簡単に書いた記事書きました。
https://taitoajiki.hatenablog.com/entry/2019/10/21/232910

いざ、実装

<template>
<v-text-field solo :counter="10" :rules="bbb"></v-text-field>
</template>
<script src="./Rules.ts"></script> 

  private bbb: any = [
    (v: any) => !!v || "Name is required",
    (v: any) => this.nameRules(v)
  ];

   private nameRules(value: any): any {
    return value.length <= 10 || "Name must be less than 10 characters";
  }

ポイント

rulesは配列にすること。

入力文字されたがコードのvに入ってくること。

そしてbbbの中の関数はfalseの場合に何の文字を
falseのときにどんな文字を表示をさせるのかが大事です。

説明

nameRulesを説明すると、returnを見てもらうと
入力文字が10文字以下の場合はtrueを返します。
そして10文字以上のはfalseとなって表示する文字が返却される。
という感じです。

はてなブログ、Twitterやってます。

Twitterを始めました。始めたばかりで友達少ないのでフォロー待ってます!
Twitterはこちら⬇︎
https://twitter.com/apasn1
はてなブログはこちら⬇︎
https://taitoajiki.hatenablog.com/

Vuetifyのvalidation公式ドキュメント

https://vuetifyjs.com/ja/components/forms

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

vue create で作成したプロジェクトでコンパイルエラーが発生した場合

vue createでプロジェクトを作成

vue create my-project

質問をマニュアルで進め、無事プロジェクトの作成に成功したので開発サーバーを起動します。

cd my-project
npm run serve

エラー発生

すると、次のようなエラーが

 ERROR  Failed to compile with 1 errors                                         

error  in ./src/main.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: [BABEL] <project-path>/my-project/src/main.js: Package exports for 
'<project-path>my-project/node_modules/@babel/helper-compilation-targets'
do not define a '.' subpath (While processing: 
"<project-path>/my-project/node_modules/@vue/cli-plugin-babel/preset.js")

解決策

Node.jsのバージョンを13.2.0以上にあげる必要があります。

Node.jsのバージョンを上げる方法

nをインストール

Node.jsのバージョン管理ツールであるnをインストールします。

npm install -g n

最新バージョンを確認

n --stableで安定版、n --latestで最新版を確認できます。
2019/1/20 現在の実行結果です。

n --stable
12.14.1
n --latest
13.6.0

最新版をインストール

n latest

sudoが要求されたら、sudo n latestのようにして実行してください。

インストールされたかバージョンを確認します。

node -v

もう一度プロジェクトを作成。無事解決することができました。
スクリーンショット 2020-01-19 17.18.50 1.png

参考サイト

Error: Package exports for /node_modules/@babel/helper-compilation-targets' do not define a '.' subpath
Node.jsとnpmをアップデートする方法 | Rriver

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

[Vue.js] VSCode でファイル保存時に自動整形する設定をメモしておく

はじめに

タイトルのとおり。
Vue.js + VSCode の環境で、ファイル保存時に自動整形する際の設定で時間がかかったのでメモを残します。
あくまで自分のためのメモなので細かい情報とか説明は載せていないです。

環境

バージョン 備考
macOS 10.14.x ( Mojave )
Node.js v10.16.3
npm v6.9.0
yarn v1.21.1
Vue.js v2.6.10

設定

package.json

package.json
{
  "name": "hogehoge",
  "private": true,
  "dependencies": {
    "vue": "2.6.10",
    "vue-eslint-parser": "6.0.4",
    "vue-loader": "15.7.1",
    "vue-router": "3.0.7",
    "vue-template-compiler": "2.6.10",
    "vuex": "3.1.1"
  },
  "devDependencies": {
    "eslint": "6.8.0",
    "eslint-config-prettier": "6.9.0",
    "eslint-plugin-prettier": "3.1.2",
    "eslint-plugin-vue": "6.1.2",
    "webpack-dev-server": "3.7.2"
  }
}

上記を設定した状態で npm install or yarn install で必要な npm モジュールが環境にインストールされる。

.eslintrc.js

.eslintrc.js
module.exports = {
  plugins: [
    'vue'
  ],
  extends: [
    'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
    'vue/html-closing-bracket-newline': [2, {'multiline': 'never'}],
    'no-extra-parens': 1,
    'no-multi-spaces': 2,
    'no-multiple-empty-lines': [2, {'max': 1}],
    'func-call-spacing': [2, 'never'],
    'no-unneeded-ternary': 2,
    'semi': [2, 'never'],
    'quotes': [2, 'single'],
    'no-var': 2,
    'indent': [2, 2],
    'space-in-parens': [2, 'never'],
    'no-console': 0,
    'comma-spacing': 2,
    'computed-property-spacing': 2,
    'key-spacing': 2,
    'keyword-spacing': 2,
  }
}

settings.json

settings.json
{
  "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
  "typescript.format.insertSpaceBeforeFunctionParenthesis": true,
  "editor.tabSize": 2,
  "editor.formatOnSave": false,
  "eslint.enable": true,
  "files.associations": {
    "*.vue": "vue"
  },
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "vue"
  ],
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

あ、一応上記の設定にあたっての備忘録がてらに。。。
最初、こちら を参考に設定していたのだが、 eslint.validate の設定で

ワーニング箇所
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
      "language": "vue",
      "autoFix": true,
    },
  ],

としていたところ、

Auto Fix is enabled by default. Use the single string form.

というワーニングが発生していた.
ググってみたところ ESLint not working in VS CODE? で Tips が紹介されていたので、それを元に修正して前掲の形にしたところ、ワーニングは解消された。

あと こちら にも当該ワーニング箇所の記述方法が示されていた

参考

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

【Vue.js】Vueコンストラクタ関数をWebコンソール上で見つける方法

Vue コンストラクタ関数とは?

Vue.jswebpack などのバンドラと共に使用している方にとっては、この Vue のことです:

import Vue from 'vue';
       ^^^

<script> タグを貼り付けるだけのいわゆる「CDN版」の Vue.js を利用している場合は window.Vue で簡単に Vue コンストラクタ関数を参照できますが、バンドラを用いてビルドされている場合においても Vue コンストラクタ関数を参照できる方法を紹介します。

vue_constructor_func_01.png

Vue コンストラクタ関数の参照を得る方法

以下のスクリプトをWebコンソール上で実行することで、Vue コンストラクタ関数を参照することができます:

const Vue = (() => {
  const el = [].find.call(document.all, el => el.__vue__);
  if (!el) {
    return;
  }
  let Vue = Object.getPrototypeOf(el.__vue__).constructor;
  while (Vue.super) {
    Vue = Vue.super;
  }
  return Vue;
})();

Vue 公式のブラウザ拡張である Vue.js Devtools の実装を参考にしました。
Internet Explorer 11 に対応した実装が必要であれば、こちらの Gist を参照してください。

応用

プロダクションビルドされていても Vue.js Devtools を有効にする

2019年6月に Apple の SwiftUI のチュートリアルサイトが Vue.js で構築されていることが話題になりましたが、プロダクションビルドされたものにも関わらず Vue.js Devtools が有効となっていることにも驚いた方が多かったようです。

この Twitter のやりとりをご覧になった @mottox2 さんがブレークポイントを挿入して Vue コンストラクタ関数を見つける方法1を紹介されていましたが、やり方を変えたものがこちらのスクリプトとなります:

(() => {
  if (!__VUE_DEVTOOLS_GLOBAL_HOOK__) {
    return;
  }
  const el = [].find.call(document.all, el => el.__vue__);
  if (!el) {
    return;
  }
  let Vue = Object.getPrototypeOf(el.__vue__).constructor;
  while (Vue.super) {
    Vue = Vue.super;
  }
  Vue.config.devtools = true;
  __VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = Vue;
})();

上記スクリプトをコンソールで実行後、デベロッパーツールを開き直すと "Vue" タブが出現します。

vue_constructor_func_02.png

バージョン情報の取得・ランタイム限定ビルドであるか判定する

ランタイムビルド・完全ビルドについて詳しくはVue.js 公式ガイドの「さまざまなビルドについて」を参照してください。

(() => {
  const el = [].find.call(document.all, el => el.__vue__);
  if (!el) {
    return;
  }
  let Vue = Object.getPrototypeOf(el.__vue__).constructor;
  while (Vue.super) {
    Vue = Vue.super;
  }
  console.log('Vue.version:', Vue.version);
  console.log('Vue.compile:', Vue.compile);
})();

ランタイム限定ビルドの場合は、 Vue.compileundefined となっています:
vue_constructor_func_04.png

完全ビルドの場合は、 Vue.compile が定義されています:
vue_constructor_func_03.png

状態を変更したり特定のページに遷移したりする

Vue コンストラクタ関数を見つける方法と近い方法で ViewModel を見つけることもできます2
ViewModel を見つければ、特定の状態に変更したり、特定の画面に遷移することなどが可能です:

(async () => {
  const $root = [].find.call(document.all, el => el.__vue__).__vue__.$root;
  await $root.$store.dispatch('HOGE_ACTION'); // Vuex
  $root.$router.push(`/fuga/path`); // Vue Router
})();

投稿完了画面など、実際に操作して遷移させるのが面倒な画面の CSS を書いたりするときに便利かもしれません。

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

obniz-nobleでBLEスキャンをグラフ表示してみた

obniz-nobleは手軽に使えていい感じですね!

今回は、ブラウザからESP32にobniz-nobleで接続して、周辺のBLEデバイスのRSSIをグラフ表示してみます。
完成図はこんな感じです。

image.png

ブラウザから開いたのち、obniz idを入力して、接続ボタンを押下すると、1秒間隔でRSSIを取得して時系列にグラフ表示してくれます。

本ツールを作成したのは、Androidでも同様のツールがあるのですが、最近AndroidのBLEスキャン間隔が定期的に長くなってしまって使いにくくなったためです。
また、ブラウザで見ると大きな画面で確認できるし、M5StickCであれば、BLEスキャナーを持ち歩くことができます。

試しに動かせるようにGitHubに上げておきました。
 https://github.com/poruruba/ble_scanner

以下をブラウザから開くことができます。
 https://poruruba.github.io/ble_scanner/

(2020/1/19 修正)
・BLEスキャンロストの制御を追加しました。5秒間、RSSIの更新がなければ、ロストとみなすようにしました。
・グラフ上の表示しない点は、値としてNaNをすればよいようです。

使うツール

・obniz-noble
 https://github.com/obniz/obniz-noble
 今回の主役です。ESP32のObnizOS搭載デバイスをBLEセントラルにできます。

・Chart.js
 https://www.chartjs.org/
 Javascriptでグラフ表示するためのライブラリです。

・chartjs-plugin-colorschemes
 https://nagix.github.io/chartjs-plugin-colorschemes/
 グラフのLineの色を適当に選んでくれるプラグインです。

・Bootstrap(v3.4.1)
 https://getbootstrap.com/docs/3.4/
 超有名なCSS等を使ったWebフレームワークです。

・Vue(v2.x)
 https://jp.vuejs.org/index.html
 Javascriptフレームワークです。データの双方向バインディングなどが特徴です。

あとは、最新のobnizOSが書き込まれたESP32が手元にある前提です。私はM5StickCを使いました。

Javascriptソースコード

Javascriptのソースコードを載せちゃいます。

start.js
'use strict';

//var vConsole = new VConsole();

var noble;

var devices = [];
const NUM_OF_DATA = 50;
const UPDATE_INTERVAL = 1000;
const LOST_INTERVAL = 5000;
var timer = null;

var vue_options = {
    el: "#top",
    data: {
        progress_title: '',

        obniz_id: '',
        device: null,
        num_of_data: NUM_OF_DATA,
        update_interval: UPDATE_INTERVAL,
        obniz_connected: false,
    },
    computed: {
    },
    methods: {
        obniz_connect: function(){
            noble = obnizNoble(this.obniz_id);

            noble.on('stateChange', (state) => {
                if (state === 'poweredOn') {
                    this.obniz_connected = true;
                    noble.startScanning([], true);

                    this.interval_change();
                } else {
                    this.obniz_connected = false;
                    noble.stopScanning();
                }
            });

            noble.on('discover', (peripheral) => {
                var device = devices.find(item => item.peripheral.address == peripheral.address);
                if( device ){
    //                device.peripheral = peripheral;
                    device.peripheral.rssi = peripheral.rssi;
                    device.counter = 0;
                }else{
    //                var peri = peripheral;
                    var peri = {
                        address: peripheral.address,
                        addressType: peripheral.addressType,
                        connectable: peripheral.connectable,
                        advertisement: {
                            serviceUuids: peripheral.advertisement.serviceUuids,
                            manufacturerData: peripheral.advertisement.manufacturerData,
                            localName: peripheral.advertisement.localName,
                            txPowerLevel: peripheral.advertisement.txPowerLevel,
                        },
                        rssi: peripheral.rssi,
                    };

                    devices.push({
                        peripheral: peri,
                        display: "display",
                        datasets: [],
                        counter: 0,
                    });
                }
            });
        },
        interval_change: function(){
            if( timer != null ){
                clearTimeout(timer);
                timer = null;
            }
            timer = setInterval(() =>{
                this.update_graph();
            }, this.update_interval);
        },
        update_graph(){
            for( var i = 0 ; i < devices.length ; i++ ){
                if( devices[i].counter * this.update_interval < LOST_INTERVAL ){
                    devices[i].datasets.unshift(devices[i].peripheral.rssi);
                    devices[i].counter++;
                }else{
                    devices[i].datasets.unshift(NaN);
                }
            }

            var current_datasets = [];
            for( var i = 0 ; i < devices.length ; i++ ){
                current_datasets.push({
                    label: devices[i].peripheral.advertisement.localName ? devices[i].peripheral.advertisement.localName : devices[i].peripheral.address,
                    data: [],
                    fill: false,
                    hidden: devices[i].display != "display"
                });
            }

            if( current_datasets.length > 0 ){
                for( var i = 0 ; i < current_datasets.length ; i++ ){
                    for( var j = 0 ; j < this.num_of_data ; j++ ){
                        if( j > devices[i].datasets.length ){
                            current_datasets[i].data[this.num_of_data - 1 - j] = NaN;
                        }else{
                            current_datasets[i].data[this.num_of_data - 1 - j] = devices[i].datasets[j];
                        }
                    }
                }
                var labels = [];
                for( var i = 0 ; i < this.num_of_data ; i++ ){
                    labels.push(i - this.num_of_data + 1);
                }
                myChart.data.datasets = current_datasets;
                myChart.data.labels = labels;
                myChart.update();
            }
        }
    },
    created: function(){
    },
    mounted: function(){
        proc_load();
    }
};
vue_add_methods(vue_options, methods_utils);
var vue = new Vue( vue_options );

var ctx = $('#chart')[0].getContext('2d');
var myChart = new Chart(ctx, {
    type: 'line',
    data: {
        labels: [],
        datasets: []
    },
    options: {
        animation: false,
        scales: {
            yAxes: [{
                scaleLabel: {
                    display: true,
                    labelString: 'RSSI [dB]'
                }
            }]
        },
        legend: {
            position: "bottom",
            onClick: function(e, item){
                vue.device = devices[item.datasetIndex];
            }
        },
        plugins: {
            colorschemes: {
                scheme: 'brewer.Paired12'
            }
        }
    }
});

各関数の説明を付記しておきます。

・obniz_connect()
obnizと接続します。接続が完了すると、以下のコールバックが呼ばれます。

 noble.on('stateChange', (state) => {

そこで、BLEスキャンを開始します。
すると、スキャンに引っかかったBLEデバイスが以下のコールバックで通知されるようになります。

 noble.on('discover', (peripheral) => {

このコールバックの中で、BLEアドレスを見て、新しいデバイスであれば内部の配列に追加し、すでにある場合は、RSSI値を更新します。

・interval_change()
グラフの再描画の間隔を変更します。一番最初のグラフ再描画ルーチンの開始にも使います。

・ update_graph()
内部のデバイス用の配列に格納しておいた各BLEデバイスの最新RSSI値を取り出し、内部の履歴用の配列に追加します。この時先頭に追加します。

そして、履歴用の配列の先頭から指定された数分だけのデータを取り出し、グラフの再描画を行います。指定された数分に満たないデバイスは、NaNとして表示対象から外しています。
ちなみに、グラフのセットアップは、以下の部分です。

 var myChart = new Chart(ctx, {

以下の指定は、凡例を選択したときに、そのBLEデバイスの詳細を表示させるためのものです。(対象BLEデバイスのグラフの非表示/表示の切り替えも可能です)

        legend: {
            position: "bottom",
            onClick: function(e, item){
                vue.device = devices[item.datasetIndex];
            }
        },

以下の部分は、今回お世話になったプラグインの指定です。

        plugins: {
            colorschemes: {
                scheme: 'brewer.Paired12'
            }
        }

HTMLソースコード

最後に、HTMLソースです。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  <title>BLEスキャン</title>

  <script src="js/methods_utils.js"></script>
  <script src="js/vue_utils.js"></script>

  <script src="dist/js/vconsole.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<!--
  <script src="https://unpkg.com/obniz/obniz.js"></script>
  <script src="https://unpkg.com/m5stickcjs/m5stickc.js"></script>
-->

  <script src="https://unpkg.com/obniz-noble/obniz-noble.js" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
  <script src="https://unpkg.com/chartjs-plugin-colorschemes"></script>
</head>
<body>
    <div id="top" class="container">
        <h1>BLEスキャン</h1>
        <div class="form-inline">
            <label>obniz id</label>
            <input type="text" class="form-control" v-model="obniz_id" v-bind:readonly="obniz_connected">
            <button v-if="!obniz_connected" class="btn btn-default btn-sm" v-on:click="obniz_connect">接続</button>
        </div>

        <div class="form-inline">
            <label>表示数</label>
            <select class="form-control input-sm" v-model.number="num_of_data">
                <option value="10">10</option>
                <option value="20">20</option>
                <option value="50">50</option>
                <option value="100">100</option>
            </select>
            <label>更新間隔</label>
            <select class="form-control input-sm" v-model.number="update_interval" v-on:change="interval_change">
                <option value="500">0.5s</option>
                <option value="1000">1s</option>
                <option value="5000">5s</option>
                <option value="10000">10s</option>
                <option value="60000">60s</option>
            </select>
        </div>
        <br>
        <canvas id="chart"></canvas>
        <br>
        <div v-if="device" class="panel panel-default">
            <div class="panel-heading">
                <div v-if="device.peripheral.advertisement.localName">
                    {{device.peripheral.advertisement.localName}}
                </div>
                <div v-else>
                    {{device.peripheral.address}}
                </div>
            </div>
            <div class="panel-body">
                <div class="form-inline">
                    <label>グラフ表示</label> <select class="form-control input-sm" v-model="device.display">
                        <option value="display">表示</option>
                        <option value="hidden">非表示</option>
                    </select>
                </div>
                <label>localName</label> {{device.peripheral.advertisement.localName}}<br>
                <label>address</label> {{device.peripheral.address}}<br>
                <label>RSSI</label> {{device.peripheral.rssi}}<br>
                <label>addressType</label> {{device.peripheral.addressType}}<br>
                <label>connectable</label> {{device.peripheral.connectable}}<br>
                <label>serviceUuids</label> {{device.peripheral.advertisement.serviceUuids}}<br>
                <div v-if="device.peripheral.advertisement.manufacturerData">
                    <label>manufacturerData</label> {{device.peripheral.advertisement.manufacturerData.toString('hex')}}<br>
                </div>
                <div v-if="device.peripheral.advertisement.txPowerLevel">
                    <label>txPowerLevel</label> {{device.peripheral.advertisement.txPowerLevel}}<br>
                </div>
            </div>
        </div>
        <br>


        <div class="modal fade" id="progress">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h4 class="modal-title">{{progress_title}}</h4>
                    </div>
                    <div class="modal-body">
                        <center><progress max="100" /></center>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="js/start.js"></script>
</body>

Vue.jsやBootstrapを使い倒しています。
その他、細かなファイルがありますが、GitHubをご参照ください。

以上

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

vueのtiptapでiframelyで生成した外部コンテンツをiframeで埋め込む

概要

vueでWYSIWYGエディタのtiptapで他のWebサイトを埋め込み表示する方法を紹介します。

手順

  • Iframelyというサービスを使います
  • tiptapのカスタムノードを定義してIframelyのURLをiframeで表示します

詳細

Iframelyでsign upしてAPI Keyを取得する

https://iframely.com/ からsign upして、ユーザー登録するとAPI Keyが発行されるのでこれを利用します。月1万回まで無料で利用できます。

API Keyの例) 58xx1axxbxxxcca68fxxxdb

sign up後は、ホームページに自分のAPI Keyが表示されます。
スクリーンショット 2020-01-19 8.58.13.png

tiptapのカスタムノードを定義する

以下のようなjsファイルを作成してvueのコンポーネントと同じフォルダに用意します。
toDomのところがポイントで、配列を多用して子ノードを定義し、以下のようなhtmlを生成さます。
公式のガイドのIframely embeds for CKEditor oEmbed pluginというページCKEditorというWYSIWYGエディタでの実装例があったので参考にしました。

生成したいHTML

<div class="iframely-embed">
  <div class="iframely-responsive">
    <iframe src="//cdn.iframe.ly/api/iframe?app=1&api_key=APIキー&url=エンコードされたURL" 'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
  </div>
</div>

tiptapのカスタムノードを生成するjs

tiptapは、ProseMirrorというWeb上でリッチテキストエディターを構築するためのツールキットを利用していて、仕様はそちらに従う必要があります。その仕様が膨大で理解するのに時間がかかりそうだったので、試行錯誤しながら進めるとできました。

Embed.js
import { Node } from 'tiptap'

export default class Embed extends Node {

  get name() {
    return 'embed'
  }

  get schema() {
    return {
      attrs: {
        src: {
          default: null,
        }
      },
      group: 'block',
      selectable: false,
      parseDOM: [{
        tag: 'iframe',
        getAttrs: dom => ({
          src: dom.getAttribute('src'),
        }),
      }],
      toDOM: node => ['div', { class: 'iframely-embed' },
        ['div', { class: 'iframely-responsive', style: 'padding-bottom: 66.6667%; padding-top: 120px;' },
          ['iframe', {
            src: '//cdn.iframe.ly/api/iframe?app=1&api_key=58xx1axxbxxxcca68fxxxdb&url=' + encodeURIComponent(node.attrs.src), // ここは自分のAPI Keyを設定する
            frameborder: "0",
            allow: "autoplay; encrypted-media",
          }, 0]
        ]
      ],
    }
  }

  commands({ type }) {
    return attrs => (state, dispatch) => {
      const { selection } = state;
      const position = selection.$cursor
        ? selection.$cursor.pos
        : selection.$to.pos;
      const node = type.create(attrs);
      const transaction = state.tr.insert(position, node);
      dispatch(transaction);
    };
  }

}

editorのコンポーネントでcommand経由でurlを渡してノードを表示する

urlの入力フォームなどは省略しますが、editor.commandからembedを呼び出すことでnodeの挿入が可能になります。

<template>
  <div class="editor">
    <editor-content class="editor__content" :editor="editor" />
  </div>
</template>

<script>
import { Editor, EditorContent } from 'tiptap'
import {
  HardBreak,
  Heading,
  Bold,
  Italic,
  History,
  TrailingNode,
} from 'tiptap-extensions'
import Embed from './Embed.js'

export default {
  components: {
    EditorContent,
  },
  data() {
    return {
      editor: new Editor({
        extensions: [
          new HardBreak(),
          new Heading({ levels: [1, 2, 3] }),
          new Bold(),
          new Italic(),
          new History(),
          new TrailingNode(),
          // ここで指定する
          new Embed(),
        ],
        content: "",
      }),
    }
  },
  beforeDestroy() {
    this.editor.destroy()
  },
  methods: {
    embed() {
      this.editor.commands.embed({ src: 'https://news.yahoo.co.jp/pickup/6348622' });
    },
  }
}
</script>

<style lang="scss">
...省略
</style>

埋め込み表示のイメージ
image.png

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

初心者によるプログラミング学習ログ 214日目

100日チャレンジの214日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

214日目は

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