- 投稿日:2020-01-19T22:37:38+09:00
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]と表示される
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>と表示される。
<script> Vue.component('コンポーネントの名前',{ template: 'htmlに表示させたい内容' }) </script>
- 投稿日:2020-01-19T21:40:05+09:00
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=reactReactを入れたアプリケーションがある場合は、Vueを指定します。
% rails new vueapp --webpack=vueyarn add
Vueで作ってあるアプリケーションには、Reactのモジュールをインストールします。prop-typesはWebpackerがデフォルトで入れるものですが、必須ではありません。
% yarn add @babel/preset-react babel-plugin-transform-react-remove-prop-types prop-types react react-domReactで作ってある場合は、Vueのモジュールをインストールします。
% yarn add vue vue-loader vue-template-compiler vue-turbolinksbabel.config.js
ルートにあるbabel.config.jsは、Reactで生成したものを使います。つまり、すでにReactならそのままにし、VueならReact用のbabel.config.jsを上書きします。
config/webpacker.yml
config/webpacker.ymlは、元からあるものを使い、Reactを加える場合は
.jsx
を、Vueを加える場合は.vue
を追加します。config/webpacker.ymlextensions: - .vue - .jsxconfig/webpacker/
ReactにVueを加える場合は、Vueで生成したものからconfig/webpacker/loaders/vue.jsをコピーします。
また、config/webpacker/environment.jsはVueのもので上書きします。
両方動かしてみる
次のような感じで両方を動かすapplication.jsを書いて動けば成功です。
app/javascript/packs/application.jsrequire("@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を切り替える、ということになるでしょう。
- 投稿日:2020-01-19T21:25:26+09:00
【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.jsvar 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)
- 初心者にもわかりやすく説明されているため、オススメです。
- 投稿日:2020-01-19T21:01:41+09:00
vuejs 外部css読込
<script> ... </script> <style> @import "./css/style.css" </style>
- 投稿日:2020-01-19T20:54:49+09:00
Vue DevtoolsがVue.js not detectedと表示される際の対処法
- 投稿日:2020-01-19T18:12:47+09:00
Flask + Vue.js + GitHub Pages で作ったTCGカードの検索アプリ
Cryspe-Prices
まずはじめに作ったアプリについて紹介します。
アプリのリンク : https://github.com/knakajima3027/cryspe-pricesCryptSpells
ブロックチェーン技術を使ったトレーディングカードゲームです。ブロックチェーン技術により、カードの種類ごとにゲーム内での発行枚数が制限されており、カードに価値が付与されるため、ユーザー間でカードの売買が行われています。
作ったきっかけ
CryptSpellsをプレイする上で特定のカードを入手したい時には、他のユーザーからカードを売ってもらう必要があります。しかし、カードの販売を行えるC to Cサービスが複数あるため、特定のカードをなるべく安く買いたいという場合、各サービスの出品情報を全て調べる必要があります。
アプリの概要
そこで,各カード販売サービスの出品情報を定期的にスクレイピングして、一つのデータベースに情報をまとめることで、各カード販売サイトをそれぞれチェックすることなく、一括でカードの検索を行えるアプリを作りました。
利用技術
利用技術は次の通りです。
サーバーサイド
- Python
- Flask
- SQLAlchemy
- Beautiful Soup
- Heroku
フロントエンド
- Vue.js
- Vue-cli
- Vuetify.js
全体構成
スクレイピング
スクレイピングには、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にデプロイできます。
- 投稿日:2020-01-19T18:12:47+09:00
Flask + Vue.js + GitHub Pages で作ったカード検索アプリ
Cryspe-Prices
まずはじめに作ったアプリについて紹介します。
アプリのリンク : https://github.com/knakajima3027/cryspe-pricesCryptSpells
ブロックチェーン技術を使ったトレーディングカードゲームです。ブロックチェーン技術により、カードの種類ごとにゲーム内での発行枚数が制限されており、カードに価値が付与されるため、ユーザー間でカードの売買が行われています。
作ったきっかけ
CryptSpellsをプレイする上で特定のカードを入手したい時には、他のユーザーからカードを売ってもらう必要があります。しかし、カードの販売を行えるC to Cサービスが複数あるため、特定のカードをなるべく安く買いたいという場合、各サービスの出品情報を全て調べる必要があります。
アプリの概要
そこで,各カード販売サービスの出品情報を定期的にスクレイピングして、一つのデータベースに情報をまとめることで、各カード販売サイトをそれぞれチェックすることなく、一括でカードの検索を行えるアプリを作りました。
利用技術
利用技術は次の通りです。
サーバーサイド
- Python
- Flask
- SQLAlchemy
- Beautiful Soup
- Heroku
フロントエンド
- Vue.js
- Vue-cli
- Vuetify.js
全体構成
スクレイピング
スクレイピングには、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にデプロイできます。
- 投稿日:2020-01-19T17:45:25+09:00
Vue TypeScript バリデーションチェック
Vuetifyのrulesを使う。
Vuetifyの
rules
を使うことで比較的簡単に実装できます。
なので今回はvuetify
のrules
を使います。前にはてなブログで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公式ドキュメント
- 投稿日:2020-01-19T17:20:54+09:00
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参考サイト
Error: Package exports for /node_modules/@babel/helper-compilation-targets' do not define a '.' subpath
Node.jsとnpmをアップデートする方法 | Rriver
- 投稿日:2020-01-19T16:45:44+09:00
[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
oryarn install
で必要な npm モジュールが環境にインストールされる。.eslintrc.js
.eslintrc.jsmodule.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 が紹介されていたので、それを元に修正して前掲の形にしたところ、ワーニングは解消された。あと こちら にも当該ワーニング箇所の記述方法が示されていた
参考
- 投稿日:2020-01-19T16:08:15+09:00
【Vue.js】Vueコンストラクタ関数をWebコンソール上で見つける方法
Vue コンストラクタ関数とは?
Vue.js を webpack などのバンドラと共に使用している方にとっては、この
Vue
のことです:import Vue from 'vue'; ^^^
<script>
タグを貼り付けるだけのいわゆる「CDN版」の Vue.js を利用している場合はwindow.Vue
で簡単に Vue コンストラクタ関数を参照できますが、バンドラを用いてビルドされている場合においても Vue コンストラクタ関数を参照できる方法を紹介します。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 が有効となっていることにも驚いた方が多かったようです。
.@Apple is using @vuejs pic.twitter.com/RGZ6TIWjMj
— Rahul Kadyan (@znck0) June 3, 2019
Add debugger before new Vue() then set Vue.config.devtools = true and continue!
— Rahul Kadyan (@znck0) June 3, 2019
You have to find new Vue() in minimised code. Here search fornew n["a"]
.
この 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.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.compile
がundefined
となっています:
完全ビルドの場合は、
Vue.compile
が定義されています:
状態を変更したり特定のページに遷移したりする
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 を書いたりするときに便利かもしれません。
Vue.js Devtools を用いる方法もあります: 【Vue.js / Nuxt.js】 ブラウザのコンソールでVueオブジェクトを表示させるには - poyopoyoのブログ ↩
- 投稿日:2020-01-19T16:03:01+09:00
obniz-nobleでBLEスキャンをグラフ表示してみた
obniz-nobleは手軽に使えていい感じですね!
今回は、ブラウザからESP32にobniz-nobleで接続して、周辺のBLEデバイスのRSSIをグラフ表示してみます。
完成図はこんな感じです。ブラウザから開いたのち、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をご参照ください。以上
- 投稿日:2020-01-19T13:50:04+09:00
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が表示されます。
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.jsimport { 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>
- 投稿日:2020-01-19T06:40:42+09:00
初心者によるプログラミング学習ログ 214日目
100日チャレンジの214日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
214日目は
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) January 18, 2020
214日目
vue.jsでつくるポートフォリオ作成
とりあえず、前に作った未完成ポートフォリオの一部のコードをvueに移してみた。
cssは、sassで書いてみた#100DaysOfCode #早起きチャレンジ#駆け出しエンジニアと繋がりたい #vuejs