20210516のJavaScriptに関する記事は29件です。

kintone を TypeScriptでカスタマイズしてみる

TypeScriptの自習として、kintoneのカスタマイズのコードをTypeScriptで写経する記事です。 自習教材として、cybozu developer network の cybozu developer コミュニティ に投稿された記事等を参考にさせていただいています。 テーブルカスタマイズ テーブルを検索して条件にあったフィールドの値を別フィールドにセットする レコード詳細画面の保存成功イベント発火時。 kintoneの詳細画面の明細テーブルのtblDropドロップダウン値が "sample02" の時に、"stringOneRow"文字列1行フィールドに明細テーブルの同一行の"tableDate"日付フィールドの値をセットします。 index.tsx (() => { const EVENTS: string[] = ['app.record.create.submit', 'app.record.edit.submit']; interface KintneEvent { record: kintone.types.SavedFields; } const FINDVALUE = 'sample2'; kintone.events.on(EVENTS, (event: KintneEvent) => { const record = event.record; console.log(record); const rows = record.table.value; console.log('rows =>', rows); for (const row of rows) { console.log(row); if (row.value.tblDrop.value === FINDVALUE) { record.stringOneRow.value = row.value.tableDate.value; break; } } return event; }); })(); 環境 TypeScript "3.9.9" 開発環境 kintone-cliにて環境構築します。 $ kintone-cli dev --watch --app-name app237 にて開発用サーバーを起動して --watch オプションでコードの変更を監視させると開発時は便利です。 $ kintone-cli init --install -p app237 $ cd app237 $ kintone-cli create-template -t Customization -s -w -l -i 237 -n app237 $ kintone-cli auth --app-name app237 $ npx kintone-dts-gen --base-url https://<subdomain>.cybozu.com -u <user> -p <password> --app-id <appId> $ kintone-cli dev --watch --app-name app237 設定ファイルなど kintone-cliを利用すると自動で吐き出されるので最初に1回だけ記載しておきます。 tsconfig.json { "compilerOptions": { "typeRoots": [ "../node_modules/@types", "./source/global.d.tsx" ], "noImplicitAny": false, "jsx": "react", "target": "es2015", "lib": [ "es2015" ] }, "files" : [ "../node_modules/@kintone/dts-gen/kintone.d.ts", "./source/fields.d.ts" ], "include": [ "source/**/*.ts", "source/**/*.tsx" ] } fields.d.ts declare namespace kintone.types { interface Fields { stringOneRow: kintone.fieldTypes.SingleLineText; table: { type: "SUBTABLE"; value: { id: string; value: { tableStr: kintone.fieldTypes.SingleLineText; tableNum: kintone.fieldTypes.Number; tableId: kintone.fieldTypes.Number; tblDrop: kintone.fieldTypes.DropDown; tableDate: kintone.fieldTypes.Date; }; }[]; }; } interface SavedFields extends Fields { $id: kintone.fieldTypes.Id; $revision: kintone.fieldTypes.Revision; 更新者: kintone.fieldTypes.Modifier; 作成者: kintone.fieldTypes.Creator; レコード番号: kintone.fieldTypes.RecordNumber; 更新日時: kintone.fieldTypes.UpdatedTime; 作成日時: kintone.fieldTypes.CreatedTime; } } package.json { (略) "devDependencies": { (略) "@cybozu/eslint-config": ">=7.1.0", "@kintone/customize-uploader": "^2.0.5", "@kintone/dts-gen": "^4.0.0", "@types/react": "^16.8.16", "@types/react-dom": "^16.8.4", (略) "typescript": "^3.6.3", "webpack": "^4.30.0", "webpack-cli": "^3.2.3" }, "scripts": { "dev": "ws", "build-app237": "webpack --mode=development --config app237/webpack.config.js", "lint-all": "eslint . --ext .js,.jsx,.ts,.tsx", "lint-all-fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", "lint-app237": "eslint app237/ --ext .tsx", "lint-app237-fix": "eslint app237/ --ext .tsx --fix" } } 参考 TypeScriptの自習の際に参考になった記事をリンクします。 https://www.typescriptlang.org/ https://future-architect.github.io/typescript-guide/index.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】Vueインスタンスのライフサイクル

はじめに 仕事で使う事になったので1からVue.jsについて学んだ。 ちゃんと覚えておかないとまずそうな事を備忘録として1つ1つ残しておく。 Vueインスタンスのライフサイクル Vueインスタンスにはライフサイクルという概念があり、以下のようなライフサイクル(フェーズ)がある。 これが分かっていると、DOMの描画後に...などの柔軟な実装ができるようになる。 上記の図中の状態(プロパティ)名と書かれている部分がVueインスタンスが持つフェーズである。 各フェーズでconsole.log()を出力させるようにすると、以下の動画のように各フェーズに到達してconsole.log()が出力されているのが分かる。 動画のソースコードは以下。 sample.html <body> <div id="app"> <h1>こんにちは、{{name}}</h1> <button @click="name = '田中 太郎'">名前を変更</button> <button @click="destroy">インスタンスを破壊</button> </div> <script src=" https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script><script src="./vue.js"></script> <script> const vm = new Vue({ // el: '#app', data: { name: '山田 花子' }, beforeCreate: function () { console.log('beforeCreate'); }, created: function () { console.log('created'); }, beforeMount: function () { console.log('beforeMount'); }, mounted: function () { console.log('mounted'); }, beforeUpdate: function () { console.log('beforeUpdate'); }, updated: function () { console.log('updated'); }, beforeDestroy: function () { console.log('beforeDestroy'); }, destroyed: function () { console.log('destroyed'); }, methods: { destroy: function () { this.$destroy(); } } }) setTimeout(() => { vm.$mount('#app') }, 3000); </script> </body> ※destroyの動きについて destroyはVueインスタンスを破壊するものであるが、実際にdestroyを行うと以下の動画のようにVueの機能(ここではクリックイベントを発火しnameを書き換え、それがリアクティブに反映される)が動かない事が確認できる。 Vue.jsの勉強メモ一覧記事へのリンク Vue.jsについて勉強した際に書いた勉強メモ記事のリンクを集約した記事。 https://qiita.com/yuta-katayama-23/items/dabefb59d16a83f1a1d4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Intersection Observerの使い方 ー まとめ ー

はじめに スクロールを行った際、画面内に対象物が入った時にJavaScriptを実行するという処理を学習したため、まとめたいと思います。 もくじ Intersection Observerとは 基本的な記述 options おわり 1. Intersection Observerとは Intersection Observerをそれぞれ日本語にすると、以下のように訳されます。 Intersection 交差、交差点 Observer 監視する者、観察者 このことから、Intersection Observerは「(物体の)交差を監視する者」という意味になります。 「物体の交差」とは、配置されている物体がスクロールされることで画面内(window内)に入ったり、出たりすることを「物体の交差」と表現されます。 そして、Intersection Observerはこの交差を監視しています。 2. 基本的な使い方 まずは、以下のようにnew IntersectionObserverへcallback関数を渡してあげ、それを変数io(Intersection Observerの略)とします。 const io = new IntersectionObserver(callback); 次に、どのDOMを監視するのか、監視対象のDOMを登録します。 const io = new IntersectionObserver(callback); const item = document.querySelector('.item'); io.observe(item); 監視対象のDOMを登録するには、querySelectorで要素を取ってきて、それを変数に格納する。 と言う流れです。 上記のコードでは、class="item"がふられている要素を監視対象としたいため、classの要素をquerySelectorで取ってきて、それをitemという変数に格納しています。 そして、io.observe(item);とすることで、itemを監視対象に登録します。 こうすることで、登録されたitemが画面内に入ったり/画面外に出たりする時に、IntersectionObserverがそれを監視し、その度にcallback関数が呼び出されます。 画面内に入った時、出た時の定義方法 callbackの中身を記述していきます。 // const item = document.querySelector('.item'); const callback = function(entries, observer) { entries.forEach(entry => { if(entry.isIntersecting) { entry.target.classList.add('inview'); } else { entry.target.classList.remove('inview'); } }); } // const io = new IntersectionObserver(callback); // io.observe(item); まず、entries.foreachでどの要素が入ったのか/出たのかを確認。 そして、foreachの引数をentryとします。 このentryの中に、それぞれの要素(今回ではitemが)が含まれています。 条件分岐のところでは、entry.isIntersectingがtrueの場合、つまり今回の場合では画面内にitemが入ればclass="inview"を追加(add)、falseであればclass="inview"を削除(remove)するという記述になります。 条件分岐 if(entry.isIntersecting) { entry.target.classList.add('inview'); } else { entry.target.classList.remove('inview'); } entry.target.classList.add('inview');のtargetに、監視対象のDOMが格納されています。 そして、trueの場合そのDOMに対してclassの付与を行なっています(falseの場合はcalssを削除)。 監視対象が画面内に入った時点で監視を終えたい場合は、observer.unobserve(entry.target);と記述することで、監視を終了することができます。 const item = document.querySelector('.item'); const callback = function(entries, observer) { entries.forEach(entry => { if(entry.isIntersecting) { entry.target.classList.add('inview'); observer.unobserve(entry.target); // ← 画面内に入ったタイミングで監視を終了 } else { entry.target.classList.remove('inview'); } }); } const io = new IntersectionObserver(callback); io.observe(item); 3. options Intersection Observerは第二引数にoptionsを取ることができます。 こんな感じ。 const io = new IntersectionObserver(callback, options); optionsの定義は以下のように記述します。 const options = { root: null, rootMargin: "-100px 0px 0px 0px", // 上 右 下 左 threshold: [0, 0.5, 1] }; 主なプロパティです。 プロパティ 設定内容 root 交差対象としたい親要素の設定(基本的には現在表示されている画面で設定) rootMargin デフォルト値は0px。 上記の設定では、画面の内側から見て上から100pxたったところが交差対象となる(pxが正の数字であれば外側)。px をつけないとエラーになるので注意! threshold デフォルト値は0。threshold:1と設定すると、監視対象が画面内に全て入るまでは交差していないとみなされる。配列で値を取ることもできる。 4. おわり スクロールするたびに画像がぽんぽん出てくるサイトの裏側はこういう風になってるのかな。 Intersection Observer、上手に使いこなせるようになるぞ!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モーダルで表示するYouTube動画をボタンごとに切り替え

はじめに  今回なでしこジャパン応援団体eAstBlueのHPを作る際に調べて出てこなかった内容を書き留めたものです。この記事を見た方にとって参考になっていましたら幸いです。 モーダルでYouTubeの表示  今までHTML埋め込みの形ではやっていました。同様の方法でモーダルにそのまま埋め込んだ場合に起こった問題。モーダルを閉じても動画が止まらないことがわかりました。YouTube公式のマニュアルを参考に実装しました。 YouTube埋め込み部分のタグ <div id="ytplayer"></div> 使用するスクリプト // iframeへのYouTubeAPI埋め込み var tag = document.createElement('script'); tag.src = "https://www.youtube.com/player_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); // 'ytplayer' に <iframe> を置き換える処理 var player; function onYouTubePlayerAPIReady() { player = new YT.Player('ytplayer', { height: '100%', width: '100%', videoId: 'oEcY98MFRR8' }); } // モーダルを閉じる処理の中で入れるコード player.stopVideo(); これによってモーダルを閉じると同時に動画も止まるようになりました。 しかし、これだけでは足りません。ボタンごとに表示される動画が切り替わる必要があります。 切り替え制御の実装  それでは今回の本題です。まず埋め込む要素を必要数用意し、パラメーターを定義する変数を配列にします。 YouTube埋め込み部分のタグ <div id="ytplayer-call-nippon"></div> <div id="ytplayer-chant-vamosnippon"></div> <!-- 埋め込む数だけ定義 --> パラメータ部分の変更 var player = []; function onYouTubePlayerAPIReady() { player[0] = new YT.Player('ytplayer-call-nippon', { height: '100%', width: '100%', videoId: 'JHrh_MX69T0' }); player[1] = new YT.Player('ytplayer-chant-vamosnippon', { height: '100%', width: '100%', videoId: '9jSQvqjgNCc' }); // 埋め込む数だけ定義 } このままにすると全ての動画が表示されてしまいますのでCSSでコントロールします。 ボタンが押された際に全体からshowクラスを削除し、ボタンに対応する部分にshowクラスを付与します。 CSS [id^="ytplayer"]{ display: none; } [id^="ytplayer"].show{ display: block; } さいごに  今回紹介した方法が最適でないかもしれません。理由としてはこちらphpを使ったCMS化を考えていましてphpでコードを生成しやすい構造を考えたからです。「もっといい方法があるよ!」ということでしたら是非コメントにて教えてください!他にもプログラミングやデザインでアドバイスくださるととても嬉しいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwitchBot温湿度計のデータを Web Bluetooth API で取得する:【未完】準備や試行錯誤のメモ

この記事は、以下の記事の続きで、あれこれ試した内容などのメモです。  ●SwitchBot温湿度計を Web Bluetooth API でスキャンする【試行錯誤中】 - Qiita   https://qiita.com/youtoy/items/e28a10866dd0050251c0 なお、思ったように進まなかった部分があり、今回の記事でも完結はしていない状況です。 Web Bluetooth API でのスキャン(前回の記事の内容) 前回の記事では、例えば以下のような処理で SwitchBot温湿度計 をスキャンできるところまでは確認していました。 // パターン1 navigator.bluetooth.requestDevice({filters: [{ services: ["cba20d00-224d-11e6-9fb8-0002a5d5c51b"] }]}); // パターン2 navigator.bluetooth.requestDevice({filters: [{ name: ["WoSensorTH"] }]}); // パターン3 navigator.bluetooth.requestDevice({filters: [{ namePrefix: ["WoSensor"] }]}); 温湿度データを取得するための前準備 さて、ここまでは「過去に利用した Web Bluetooth API の仕組みを使って、とりあえずスキャンして検出できるところまで試せた」という状況でした。 その後、この続きをやろうと調べたりしていると「もしかしたら今まで行っていた BLEデバイスとの間でのデータ送受信とは異なるやり方が必要かも?」という状況になってきました。具体的には、以下にも記載された「Web Bluetooth Scanning」に関わる部分です。  ●Web Bluetooth Scanning   https://webbluetoothcg.github.io/web-bluetooth/scanning.html スキャン関連の情報・サンプルを見直してみる 追加で調べた情報の中に、以下の「Scanning API の仕様」と Chrome での対応状況の話が出てきました。 ●web-bluetooth/implementation-status.md at main · WebBluetoothCG/web-bluetooth  https://github.com/WebBluetoothCG/web-bluetooth/blob/main/implementation-status.md#scanning-api 2つ目のリンク先の内容(上の画像の内容)を見てみると、Android・Mac の Chrome で chrome://flags/#enable-experimental-web-platform-features を有効にすると使えるスキャン関連の処理があるようです。 スキャン関連の処理をコンソール上で実行してみる 自分が普段使っている Mac の Chrome で続きを進めていきます。 Chrome で chrome://flags/#enable-experimental-web-platform-features を開き、該当項目が Disable になっているのを Enable に変更してみました。そして、ブラウザの再起動を促されるのでそれに従います。 上で URL を載せていた「Web Bluetooth Scanning」の中の「1.1. Examples」を見てみると、以下のような処理が書いてあります。 これを参考にして、Chrome の開発者ツールのコンソールで実行するための簡単な処理を書いてみました。やったことは、スキャンをしている部分の一部を取り出して、フィルター設定の部分を前回利用したもの(SwitchBot温湿度計の名前を記載したもの)に変えただけです。 navigator.bluetooth.requestLEScan({ filters: [{ name: ["WoSensorTH"] }], keepRepeatedDevices: true }) そして、開発者ツールのコンソールで実行してみると、エラーはでないことが確認できました。 ちなみに、 chrome://flags/#enable-experimental-web-platform-features を Disable の状態で同じことをやると、「navigator.bluetooth.requestLEScan って関数はない」というにエラーがでます。 さて、スキャン用の処理を実行する方法は見つかったものの、スキャン結果が空白になっている気がします。もしかしたら UUID とか別の設定を使わないとダメなのかな、とか思いつつ、いったんフィルターを行わず検出できる全てのデバイスを対象にスキャンしてみました。 navigator.bluetooth.requestLEScan({ acceptAllAdvertisements: true }) そのために上記を実行してみたのですが、またデバイスが 1つも検出されないような... OS(デバイス)を変えてスキャン関連の処理をコンソール上で実行してみる ここで試しに、別の Windows 10 のマシンで実行してみることにました。 上で見つけていた情報によると、Windows であれば chrome://flags/#enable-experimental-web-platform-features が Disable でも OK なのかな、と思ったのですがエラーがでたので Mac と同様に Enable にして試しました。 Chrome の開発者ツールのコンソールで navigator.bluetooth.requestLEScan({ acceptAllAdvertisements: true }) を実行すると、スキャン結果の表示部分にたくさん情報が表示されました。 そこで、今度は以下の UUID でフィルタする設定で実行してみました。 navigator.bluetooth.requestLEScan({ filters: [{ services: ["cba20d00-224d-11e6-9fb8-0002a5d5c51b"] }], keepRepeatedDevices: true }) 無事、1台分の情報が出てきたのを確認できました。 しかし、この後の続きでいろいろやっていると動作が不安定な感じが... (スキャンを実行した後の許可を求めるダイアログが出なくなったり、何度かやり直してると表示されたり、というような挙動に) OS(デバイス)を変えてスキャン関連の処理を実行する さらに OS というかデバイスを変更してみます。 スキャン関連の情報を調べていて以下の Android を使った事例があったので、Androidスマホを使って試してみます。  ●Web Bluetooth を使ってみよう!その1 - 芳和システムデザイン   https://houwa-js.co.jp/exe/2021/03/20210316/ Androidスマホだと開発者コンソールであれこれやるのが厳しそうなので、HTMLファイルに書いてそれを実行する形にします(Netlify を使ったりしつつ)。具体的には、以下の内容のファイルを作りました。 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Web Bluetooth API によるスキャン</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.css" /> </head> <body> <section class="section"> <div class="container"> <h1 class="title">操作用ボタン</h1> <div class="buttons" style="margin-top: 1.5rem"> <button class="button is-success is-light" type="button" onclick="onStartButtonClick()" > スキャン </button> </div> </div> </section> <script> async function onStartButtonClick() { await navigator.bluetooth.requestLEScan({ filters: [{ services: ["cba20d00-224d-11e6-9fb8-0002a5d5c51b"] }], keepRepeatedDevices: true, }); } </script> </body> </html> こちらを Web上にアップして Androidスマホで実行したところ、文字が薄くて非常に見づらいですが、以下のように 1つだけスキャン結果が出てきました。 温湿度データを取得するための下準備 この後は Androidスマホ上で処理を実行する形で続きを進めてみます。 PC の Chrome と Androidスマホの Chrome を連携させる その際、Androidスマホ上の Chrome のコンソールへの出力を確認できるように、以下の仕組みを利用することにします。  ●Remote debug Android devices - Chrome Developers   https://developer.chrome.com/docs/devtools/remote-debugging/ とりあえず上記の仕組みのさわりの部分だけ書いておくと、以下のようになります。 PC と接続した Androidスマホを、PC から USBデバッグできる状態にする PC と Androidスマホをつないだ状態で、PC で Chrome を開く 3. PC の Chrome で chrome://inspect#devices を開く 上記の手順3 で開いたページで「Discover USB devices」にチェックが入っていれば、USB で接続した Androidスマホ(※ USBデバッグの設定がされているもの)を PC上から操作できたりします。 試行錯誤していたら思った以上にメモの内容が増えてしまったのもあり、この続きは別の記事にしようと思います。 まとめ SwitchBot温湿度計のデータを Web Bluetooth API で取得するには、前回の記事で試していた navigator.bluetooth.requestDevice( ではなく、 navigator.bluetooth.requestLEScan を利用する形になりそうというのが分かりました。 さらに、 navigator.bluetooth.requestLEScan を利用するためには Chrome で chrome://flags/#enable-experimental-web-platform-features を有効にする必要があることが分かり、いろいろ試していると自分の環境(Mac、Windows、Android)では Android 以外の OS で navigator.bluetooth.requestLEScan が意図した通りに動かなそうである状況のように見えました。 進捗はあったものの今回の記事で完結せず、次こそは完結編にできればと思います。 (知見はいろいろ得られた気がするので、その点は良いかな) 【追記】 次で完結! 試行錯誤を続けて書いた次の記事で、ようやく温湿度の値を BLE経由で得ることができました。  ●SwitchBot温湿度計のデータを Web Bluetooth API で取得する【完結編】 - Qiita   https://qiita.com/youtoy/items/8e3cca2172e2c4806846
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

平成生まれのエンジニアが初めてIoTに触れるのに丁度良い、生き残りゲーム~令和最新版~のプロトタイプをobnizで作る

先日、手軽にIoTに触れられると噂の「obniz」を入手しました。 ラズパイに比べて、圧倒的に早く、楽に電子デバイスを操れるのでうっかりスキルが向上したか?と錯覚するほどです。 この手軽さを活かす先は、プロトタイプでしょ?!ということで、さっそく行動してみました。 生き残りゲームの令和最新版をプロトしよう 時代背景・コロナ感染予防・働き方・IoT・サーボモーターの動き、、、パッとピラめいたのが  生き残りゲーム レトロラブな私としては、これの令和最新版が作りたい!新しい形で対戦してみたい! これです。  コレは絶対楽しいやつにしか思えないので、プロトしてポテンシャルを測ってみたいと思います。 ちなみに、この生き残りゲーム、平成生まれの方はあまり知らないらしいですね。そんな記事をネットで見ました。 ご存じない方は「生き残りゲーム 沈没」でググってみてください。 そして、私のこの記事をこのまま読み進めていただき、私のような昭和生まれのエンジニアに対して「IoT触ってみましたよ!生き残りゲームでw」と会話のネタにしていただければ幸いです。上司や仕事仲間と良好な人間関係を構築することができることでしょう。 まん防に対応しても当時の興奮そのまま!そしてニューノーマルへ コンセプトはズバリこれ↑です 生き残りゲーム(物理的なゲーム)をIoTデバイスを駆使してリモートで対戦できるようにします。 いろいろなものが電子化され、新型コロナのおかげでさらに拍車がかかっています。そんな時だからこそ、現物にこだわりたい! 生き残りゲームの醍醐味は、球がドボンするところ。やはり物理的なモノがあってナンボです。ガシャ!ってならないと興奮しません。素朴なギミックを大事にしたいですね 仕掛けはこんな感じ さて、実現可能なのでしょうか?面白いのでしょうか?プロトしてみましょう。 出来上がったのがこちらです アイデアを可視化しそこから新たな学びを得ることがプロトの目的ですので、「さっさと作る」が肝要。 そこらへんに転がっているもので、さっさと作りましょう。本物の生き残りゲームは、もう手に入らないしね。。。 できたアウトプットも「さっさと見せて」しまいましょう。 ということで完成品がこちらです。 本人いたって真剣です(笑 2×2マスのミニマム仕様です。 縦方向に走る緑色の板がリモート参加者用のドボンバー(正式名称不明)。 ドボンバーはキングピン代わりのカーボン抵抗を介してサーボアームと直結、obnizで制御され上へ下へと動きます。 横方向のドボンバーは実機参加者用で、手動となります。 ドボン機構の再現には、材料の穴あけ容易性とそこそこの強度が必要となります。いろいろな素材を試した結果、某おもちゃショップのポイントカードと某アイドルグループのメンバーズカードを採用するに至りました。 制作概要 使ったもの obniz board 1Y サーボモーター×2 ジャンパワイヤ カーボン抵抗 古びたタッパー 無用になったメンバーズカード2枚 ビーズ 両面テープ ガムテープ 猫頭付箋 貯まりにたまったAmazonの段ボール 仕掛け リモート側のユーザインターフェースとしてLINE Botを採用 Node.jsでLINE Bot とobnizを制御 リモートユーザのドボンバー2本を 2つのサーボモータでリモート操作 作成風景 穴あけ作業がちょいちょい発生するので、ピンバイス、ドリルなどあると良いです。 Node.jsによる制御(コード) 技術要素 LINE Botを使ったユーザとのメッセージ授受 obnizを使ったサーボモータの制御 expressを使ったLINEとobnizの連携 expressをつかって手持ちのパソコンでサーブ。 ngrokをつかってインターネット上のLINEのサービスと接続した。 dobon.js // ######################################## // obniz処理部分 // Obniz_ID:自分のobniz ID(XXXX-XXXX) // ######################################## const Obniz = require('obniz'); const obniz = new Obniz('XXXX-XXXX'); // obnizと接続確立したとき obniz.onconnect = async () => { obniz.display.clear(); obniz.display.print('obniz Ready'); } //サーボの動作 const svRFunc = async (deg) => { const sv = obniz.wired('ServoMotor', { signal: 2 }); sv.angle(deg); } const svLFunc = async (deg) => { const sv = obniz.wired('ServoMotor', { gnd: 9, vcc: 10, signal: 11 }); //let degrees = i; sv.angle(deg); } // ######################################## // LINEBot イベント処理部分 // channelSecret:LINE Developers → チャネル基本設定 → チャネルシークレット // channelAccessToken:LINE Developers → Messaging API設定 → チャネルアクセストークン(長期) // ######################################## const config = { channelSecret: 'XXXXXXXXX', channelAccessToken: 'XXXXXXXXX' }; const line = require('@line/bot-sdk'); const client = new line.Client(config); // ExpressからMessaging APIイベントを渡されて処理するところ const handleEvent = async (event) => { // テキストメッセージ以外を受信したときは何も行わずresolveを返す if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // テキストメッセージを受信したとき if (event.message.text === '下のレバーを左') { // 受け付けたということを「リプライ」で先に返す await client.replyMessage(event.replyToken, { type: 'text', text: '了解' }); // obnizのサーボ動かす(ブロッキング・時間のかかる処理で一旦ここで止まる) const temp = await svRFunc(120.0); // 処理完了を「プッシュ」で送信する client.pushMessage(event.source.userId, { type: 'text', text: '下のレバーを左に動かしたよ', }); //以下 各サーボ毎にふるまいを設定 }else if (event.message.text === '下のレバーを真ん中') { await client.replyMessage(event.replyToken, { type: 'text', text: '了解' }); const temp = await svRFunc(90.0); client.pushMessage(event.source.userId, { type: 'text', text: '下のレバーを真ん中に動かしたよ', }); }else if (event.message.text === '下のレバーを右') { await client.replyMessage(event.replyToken, { type: 'text', text: '了解' }); const temp = await svRFunc(60.0); client.pushMessage(event.source.userId, { type: 'text', text: '下のレバーを右に動かしたよ', }); }else if (event.message.text === '上のレバーを左') { await client.replyMessage(event.replyToken, { type: 'text', text: '了解' }); const temp = await svLFunc(45.0); client.pushMessage(event.source.userId, { type: 'text', text: '上のレバーを左に動かしたよ', }); }else if (event.message.text === '上のレバーを真ん中') { await client.replyMessage(event.replyToken, { type: 'text', text: '了解' }); const temp = await svLFunc(90.0); client.pushMessage(event.source.userId, { type: 'text', text: '上のレバーを真ん中に動かしたよ', }); }else if (event.message.text === '上のレバーを右') { await client.replyMessage(event.replyToken, { type: 'text', text: '了解' }); const temp = await svLFunc(130.0); client.pushMessage(event.source.userId, { type: 'text', text: '上のレバーを右に動かしたよ', }); } else { // LINEから飛んできたメッセージの中身が指定の文言以外だったとき client.replyMessage(event.replyToken, { type: 'text', text: 'メニューボタンで操作してね' }); } // resolveを返す return Promise.resolve(null); } // ######################################## // Expressサーバー部分 // ######################################## const express = require('express'); const PORT = process.env.PORT || 3000; const app = express(); // 「(サーバーURL)/webhook」にアクセス(LINEサーバーからのWebhook)があったとき app.post('/webhook', line.middleware(config), (req, res) => { // 受信したイベントをターミナルに表示 console.log(req.body.events); // イベントをhandleEventに渡して1つずつ処理 Promise.all( req.body.events.map(handleEvent) ).then( result => res.json(result) ); }); // PORT番号のポートでサーバーを開始 app.listen(PORT); console.log('express runnning: PORT =', PORT); プロトタイプテストの様子 被験者には実際に離れたところから、今回開発したLINEのアプリをつかってゲームに参加していただいた。 生き残りゲーム本体の様子は別途Zoomで中継した。 プロトタイプから得られた学び 良かった点 見た目を含め実にアホくさくて非常にウケてもらえた。作ってよかったと感じた。(改めて、自分はこの手のやつが大好物だということを思い知った) 仕事の合間、WEB会議で雰囲気が悪くなった時などに、こいつをいそいそ出してくるとウケるかもしれないとおもった。やはりニューノーマルガジェットの筆頭になるか? サーボの勢いがありすぎて球が下におちるのではなく外に飛び出した時(想定外の動き)が一番盛り上がった。そういえば当時、その手のチートを繰り出す輩もいたよねと昔の思い出話に花が咲いた。このゲームを令和最新版にするにあたりこだわるポイントは物理的アクションで間違いはないと確信した。 もっと深堀したい点 昭和時代の人に当てたらどれだけウケるか? サイズ感はどれくらいがよいか?実際にプロトで触ってみて、通常版(フルスペック)をネット越しでガチでやるのはちょっと想像ができなかった。今回のような2×2またはもうちょっと大きいくらいがよいだろうか。ユースケースはやはりWEB会議のアイスブレイクなどではないだろうか 付加機能があったほうがいいのか?それともシンプルが一番なのか?なんとなくシンプルのほうが良い気がする。 当たり前だが、本物の意匠をどれだけ再現できるか?にかかっているとおもう。 リモートだとほんのり動かして穴の様子を探るチートができない点をどうするか?LINEボタンではなく、フリック入力などで力加減を伝えられる仕組みも面白いかもしれない。 やはり平成生まれには刺さらないだろうか? 完全フルオートはどうだろうか?球の回収・セッティングまで全自動。オンラインUFOキャッチャーのようなイメージ メーカーさん製品化してくれないだろうか? 生き残りゲーム~令和最新版~のプロトタイピングは以上です。 ご意見お待ちしております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

浦島太郎のVue.js

前書き 数年ぶりにJavaScript触ったら結構変わってるな…! と思ったので、私みたいな浦島太郎さんが他にもいたら役立てて欲しいなという記事です。 一応Vue3の想定で書いていますが、Vue2限定のものが混ざっていたらすみません。 想定読者層 だいたい下記レベル感を想定しています。 他の言語でプログラミング自体はできる 基礎的なLinuxコマンドが打ててアレルギーがない JavaScriptは昔ちょっとjQuery触ったことがある(一応DOMの概念はわかる) ブラウザ上で動くJavaScriptについては一応知ってる 最近イケてると噂のVue.jsを触ってみたい npmってなにさ? webpackってなにさ? 細かい部分はググればわかりそうだけど、前段階としてざっくり概念を知ったり、できることを簡単に知っておきたい 注意書き 私も学習しながらざっくり理解で進めたので、正確でない部分があるかもしれませんがご了承ください。 間違いがあれば教えて頂けると助かります。 Vue.jsってどういうもの? そもそも問題ない人は公式を読んでもらうのが一番良いと思います。 というより問題なく読める人はここまでで回れ右してもらって大丈夫です。 しかし 公式ガイドは、HTML、CSS そして JavaScript の中レベルのフロントエンドの知識を前提にしています。フロントエンドの開発が初めてであるならば、最初のステップとして、フレームワークに直接入門するのは良いアイデアではないかもしれません。基礎を学んで戻ってきましょう!他のフレームワークでの以前の経験は役に立ちますが、必須ではありません。 こんなことが書いてあって、浦島太郎な私には敷居が高かったです。 実際一度読んだだけではわからないことも多々ありました。 私が現在理解している範囲でVue.jsでできることを簡単に書くと 任意の範囲のDOM定義をvueコンポーネントとして分割して定義しておける コンポーネントを呼び出す際には引数を与えることができ、それによって描画内容に幅を持たせられる 複数のコンポーネントを組み合わせて画面を構築していくことができる SPA(Single Page Application)やSSR(Server Side Rendering)を構築できる という感じのようです。 Vue.jsってどうやって動いているの? 基本的なことですが、jQuery脳から入るとこの時点で少し疑問符が湧くので、理解を進めるため簡単に書いておきます。 表示されるWebページ(index.html)はファイルとして普通に用意されており、ユーザーはここにアクセスする index.html内にjsタグを埋め込んでおき、Vue.jsで作成され1ファイルに圧縮されたjsファイル(main.js)を読み込み、実行する。 main.jsではVueのアプリケーション・オブジェクトが作成され、ここでコンポーネントを読み込み、Vueで定義したDOMの描画を行う JavaScript初心者が躓きがちな記法や概念 JavaScriptには他の言語に慣れているとパッとわからない書き方や概念がいくつかあります。 そして、ググってもこのあたりの知識は前提として記事が書かれており、ググり方もよくわからず困ったりしました。 なので基礎知識として、よく見るなと思ったものについて最初に軽く触れておこうと思います。 ES2015(別名ES6) ECMAScriptの6th Edition、2015年に策定されたためES2015と呼ばれたりES6と呼ばれたりするようです。 現状最新のJavaScriptの言語仕様の名前、と覚えておけば概ね問題ないと思います。 JavaScriptはブラウザによって挙動が微妙に異なる部分があり、それでは困るため、共通仕様として決められたもののようです。 アロー関数 アローを使うことで、methodとかfuncとかのそれらしい英字なしに関数を定義することができます。 // 従来だとこう書く var fn = function (a, b) { return a + b; }; // アローで書く const fn2 = (a, b) => { return a + b; }; // 単一式の場合は色々省略できる const fn3 = (a, b) => a + b; // オブジェクトを返却する場合は少し書き方が変わる const fn4 = (a, b) => ({ sum: a + b }); スプレッド構文 これは他の言語にもあったりなかったり。 可変長で引数を受けたり渡したりできます。 function func (...r) { console.log(r[1]) } func(1, 2, 3, 4, 5); // => 2 通常の引数と併用も可能です function func (a, ...r) { console.log(r[1]) } func(1, 2, 3, 4, 5); // => 3 渡す時も同様です function func (a, b, c) { console.log(a + b + c) } const arr = [1, 3, 5] func(...arr); // 9 配列の中に展開も可能です const arr1 = [1, 2, 3] const arr2 = [...arr1, 4, 5, 6] console.log(arr2); // [1, 2, 3, 4, 5, 6] オブジェクト リテラルだと以下 a = { key1: value1, key2: value2, } オブジェクトのKeyValueのことはプロパティと呼びます。 プロパティにはドット区切りでも辞書形式でもアクセス可能です。 ドット区切りを使う方が一般的らしいですね。 a.key1 == a["key1"] == "value1" オブジェクトに関数の定義を入れる場合は省略記法があります。 let rgb = { r: 255, g: 128, b: 64, total1() { return this.r + this.g + this.b }, total2: function() { return this.r + this.g + this.b }, total3: () => { return this.r + this.g + this.b }, } クラス定義 constructor() は言語側で予約済。 Vue.jsを使う上ではあまり必要ないかもしれませんが、念の為。 class Hoge { constructor(args) { this.args = args // initial } method () { ... } } node.jsについて最低限 node.jsってサーバーサイドjsのことじゃなかったっけ? たしかシングルスレッドで非常にレスポンスが早いけど設計に注意が必要なWebサーバーだったような… などと浦島さんは思いました。 どうもnode.jsが示す範囲はもう少し広いようでした。 フロントのWebページ用のjsコードであっても開発中はnode.jsで管理して作成し、完成したコードをコンパイルして最終的にはフロントで使う、という開発手順になっているみたいですね。 npmとは 簡単に書くと、JavaScriptのライブラリ等を管理するためのツール。 PHPでいうcomposerだったり、Pythonでいうpipやpoetryに近い概念だと思っておけばとりあえず大丈夫だと思います。 package.jsonの中に必要なライブラリのバージョンや、パッケージの名前、製作者や連絡先などの情報を入れておけます。 場合によっては利用するライブラリのオプション指定などもこのファイル内に書いておくケースもあるようです。 また、実行コマンドをpackage.jsonの中に定義しておいて、呼び出すこともできます。 よく使うビルドコマンドなどをオプション付きで書いておいて使う、などが主な用途になると思います。 package.json { "name": "hoge", "version": "0.0.1", ... "scripts": { "dev": "コマンド" npm run ***で実行したいコマンドを定義しておく }, "dependencies": { 実行に使うライブラリとバージョンの一覧。jqueryやvueなどを指定する。 }, "devDependencies": { 開発中のみ使うライブラリ。babel、eslint、webpackなどを指定する。 } } # dependenciesとdevDependenciesに定義しておいたライブラリをnode_modules以下にインストールする npm install # package.jsonのdevで定義しておいたコマンドを実行 npm run dev このあたりについて解説するとそれだけで1本書けるので、ここでは割愛します。 webpackとは 大量のJavaScriptを書いてWeb上で使用したい場合、開発者としてはある程度の単位でファイルを分けて管理したいです。 しかし、ファイルが増えるたびにそれを読み込む定義をhtml側に逐一追加していくのは非常に大変で、何よりアホらしいですよね。 こんな課題を見事に解決してくれるのがwebpackです。 複数のjsファイルや、sassや画像までもまとめ上げ、1ファイルに出力してくれます。 html側はまとめ上げた後の1ファイルだけを常に読み込むよう指定しておけば、js側の開発過程でファイルがどう増減しようが関係ないわけです。素晴らしい。 なお身近な範囲ではwebpackを使っている人が多かっただけで、複数ファイルを1ファイルにまとめ上げる方法は他にもあるそうです。 興味があれば探してみてもいいと思います。 Vue.jsについて Getting started的なものは公式にお任せするとして、ここでは学習前にざっと頭出ししたりする用のリストとして書いていきます。 .vueファイルに含まれるもの 大きく3種類、基本的には2個です。 <template> DOM要素 <script> JavaScriptコード <style> CSS template HTMLタグを基本として、vue特有の記法やjs変数の埋め込みをしながらDOMを定義していきます。 template配下のトップレベルにはタグを1つしか指定できないため、そうできる単位でコンポーネントを切り分けることを意識しましょう。 NG <template> <div> ... </div> <div> ... </div> </template> OK <template> <div> <div> ... </div> <div> ... </div> </div> </template> v-xxx v-から始まる属性は、およそ何らかの方向性で変数をDOMに紐付けるための指定方法となっているようです。 v-html 変数を直接HTMLとして使いたい場合に用いる。 <div v-html="editableHtml"></div> v-bind タグの属性値に変数を使いたい場合に使う。 <div v-bind:name="hoge"></div> これはname属性に変数hogeの値を指定した形になる。 なおv-bindは省略可能、というか省略することの方が多いらしいです。 <div :name="hoge"></div> オブジェクト構文 v-bindに辞書を指定し、辞書の値がtrueなら有効、falseなら無効として指定できます。 わかりやすい例としてはclass指定です。 <div :class="{ red: true, blue: false }">hoge</div> v-if ifの中身が真なら描画する。v-elseもあります。 これはわかりやすいので例は省略します。 v-for forループ。v-ifとは同時に指定できないようです。 <template v-for="i in 10"> <template v-for="item in array"> <template v-for="(item, index) in array"> <template v-for="(value, key) in object"> v-on クリック等のなんらかの条件で発火するイベントを処理したい場合に指定します。 methodsの中に定義したメソッドを指定して呼び出します。 例えばonclickならv-on:clickとして指定します。 <p v-on:click="doAction">...</p> 省略して@で書く方が一般的らしいです。 <p @click="doAction">...</p> ref属性 idやcssセレクタなどはビルド時に変換されてしまい想定通りの挙動にならない可能性があるため、特定のdomに対して何かしたい場合にはrefを使うことができます。 直接HTML要素に介入するため想定外の挙動を起こす可能性があり、利用は最小限にした方が良いそうです。 <p ref="hoge"> this.$refs.hoge templateタグ templateタグ自体は描画されません。 v-forやv-ifなどで一定の範囲を括りたい時などに有用です。 transitionタグ trasitionタグ自体は描画されません。 描画時や消滅時にcssを定義しておけるもの、らしいです。 script コンポーネントごとのjs部分を定義します。 だいたい以下2種類を記述することが多いけど、他にも色々と書く必要があるケースもあります。 import export default import 外部や別ファイルに定義してあるモジュール、コンポーネントをインポートして、このコンポーネント内で使えるようにします。 import AAA from 'XXX' import { A, B, C } from 'YYY' import D from '@path/to/module' export default 最初にこれを見た時に私は「export defaultってなんぞ?」と結構謎でしたが、これは外部からモジュールとして呼ばれた時にデフォルトで参照するものを指定しているそうです。 これ自体はVueでなくJSの記法らしいですね。 よくわからなければ「おまじない」と思っておいても問題ないと思います。 Vue.jsではだいたい以下のような形でオブジェクトを定義していくことが多いと思います。 export default { ... } components importして利用するコンポーネントを指定しておきます。 export default { components: { A, B }, } 指定したコンポーネントはタグのようにしてtemplate内で使用でき、引数を渡すことも可能です。 props モジュールとして外部から呼ばれた際に受け取れる引数について定義できます。 export default { props: { hoge: String, xyz: { type: Number, default: 0, }, percentage: { type: Number, required: true, validator: (value) => { return value == parseInt(value) && value >= 0 && value <= 100 }, }, f: { type: Function, required: true, } } } data コンポーネントが使う変数を定義できます。 computed 算術プロパティを定義できます。 概ねdataやpropsと同様に扱うことができます。 値は依存先の変数が更新された際に再計算されます。 直接関数を定義するとGetterとして扱われますが、Setterも定義できます。 computed: { calcProperty: function() { ... calc process return value }, canSetProperty: { get() { ... getter process return value }, set(val) { ... setter process } }, }, watch 常に値の変更を監視する旨について定義しておけます。 1つの値の影響範囲が広かったりする場合に使うことがあるけど、基本的にはcomputedで簡素に書けることが多そうです。 watch: { propertyName (newValue, oldValue) { ... process }, } methods 関数を定義しておけます。 v-onなどから呼び出すことが多い印象ですが、様々な使い道がありそうです。 created コンポーネントのオブジェクトが作成された直後、propsやdataの設定より後に実行される。 ローカルストレージから値を取り出したり、色々できそうですね。 よく聞くライブラリ 有名どころについて少し。 Vuex 様々なコンポーネントで共通して扱える、ストアと呼ばれる保管場所を用意できるライブラリ。 グローバル変数のようなものになるため、大きいアプリケーションになってくると扱いが難しくなりそうですね。 $storeというグローバルに使用できる値が用意され、そこから値を扱う形になります。 i18n 複数言語対応を簡単にしてくれるライブラリ。 $tというグローバルに使用できる値が用意され、そこから予め定義しておいた文字列を呼び出して描画に用いるような使い方になる。 多言語対応しない場合でも文言を各vueファイルに散らばらせずに一箇所で管理できるため、採用価値は高そうです。 ライブラリが用意する便利変数は頭に大体$がついているのかもしれませんね。 プラスαで知っておくと便利な概念 記事を漁っていると時々でてきて、なんだそりゃ?とならないための予習用です。 ググるにも知識が要りますよね。JS関係では特に苦労しました。 Flux Facebookの提唱するWebアプリケーションを開発する際に用いるアプリケーションアーキテクチャです。 アプリケーションをView、Store、Dispatcher、Actionの4部品に分けて扱います。 詳しくは公式へどうぞ。 個人的にはまだ有用性を咀嚼できていませんが、たぶん巨大なアプリケーションになってくると有用性が出てくるのだと想像しています。 Atomic Design Vueコンポーネントの設計手法の1つです。 Vueにも一応公式のスタイルガイドがありますが、Atomic Designではコンポーネントの分割単位について細かく定義しています。 コンポーネントを分ける単位に悩んだら参考にすると良さそうです。 最後に フロントの技術推移は早いとは噂で聞いていましたが、実際もう基礎知識がない状態に近いなと感じました。 推移の早さのせいか、検索してもサンプルコードが動かないケースもそれなり。 ともあれ久しぶりにJS触って色々と書いたのは楽しかったです。そのうち個人サイト作る過程とかも記事にしてみたいですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Obnizで天気予報を可視化して傘予報をしてみた☂

最近、家を出てから雨を降っていることに気づき部屋までちょいちょいあり、どうにかならないかと思い作ってみました! 概要図 傘不要(晴れ)、折り畳みを持って行った方がいい場合(曇り)、傘必要(雨)の場合に分けています。 それぞれ緑・黄色・赤で表現しています。 ■コード全文 node.js const fetch = require("node-fetch"); const APIKEY = "APIKYEを入力"; const RED_WEATHERS = [ "Rain", "Snow", "Thunderstorm", "Drizzle", "Fog", "Squall" ]; const YELLOW_WEATHERS = [ "Clouds", "Mist", "Smoke", "Dust", "Haze", "Sand", "Ash", "Tornado" ]; const GREEN_WEATHERS = ["Clear"]; const Obniz = require('obniz'); const obniz = new Obniz('Obniz_ID'); // Obniz_IDに自分のIDを入れます obniz.onconnect = async function () { // RGB LEDを利用 const rgbled = obniz.wired('WS2811', { gnd: 0, vcc: 1, din: 2 }); // ディスプレイ表示(初期画面) obniz.display.clear(); obniz.display.print('Hello obniz!'); obniz.repeat(async () => { //現在の天気データ呼び出し let data = await (await fetch( `http://api.openweathermap.org/data/2.5/weather?q=Hokkaido,jp&appid=${APIKEY}` )).json(); console.log(data); let currentWeather = data.weather[0].main; if (currentWeather === undefined || currentWeather === null) { rgbled.rgb(0, 0, 0); return; } if (await isMatched(RED_WEATHERS, currentWeather)) { rgbled.rgb(255, 0, 0); obniz.display.clear(); obniz.display.print('Bring an umbrella!!'); } else if (await isMatched(YELLOW_WEATHERS, currentWeather)) { rgbled.rgb(255, 255, 0); obniz.display.clear(); obniz.display.print('Better bring an umbrella!'); } else if (await isMatched(GREEN_WEATHERS, currentWeather)) { rgbled.rgb(0, 255, 0); obniz.display.clear(); obniz.display.print('No need for umbrellas.'); } else { rgbled.rgb(0, 0, 0); console.log("no data"); } }, 30000); }; async function isMatched(array, _currentWeather) { for await (let weatherName of array) { if (_currentWeather === weatherName) { return true; } } return false; } APIの準備 OpenWeatherのAPIのページで、sign upをクリックし、必要事項を記入してアカウントを作成します。 APIを叩いて帰ってくる天気をグループに分けます。 const RED_WEATHERS = [ "Rain", "Snow", "Thunderstorm", "Drizzle", "Fog", "Squall" ]; const YELLOW_WEATHERS = [ "Clouds", "Mist", "Smoke", "Dust", "Haze", "Sand", "Ash", "Tornado" ]; const GREEN_WEATHERS = ["Clear"]; 30秒毎に結果を取得し、ディスプレイ表示とLEDの色を設定します。 今回は東京の天気で設定しています。 obniz.repeat(async () => { //現在の天気データ呼び出し let data = await (await fetch( `http://api.openweathermap.org/data/2.5/weather?q=Hokkaido,jp&appid=${APIKEY}` )).json(); console.log(data); let currentWeather = data.weather[0].main; if (currentWeather === undefined || currentWeather === null) { rgbled.rgb(0, 0, 0); return; } if (await isMatched(RED_WEATHERS, currentWeather)) { rgbled.rgb(255, 0, 0); obniz.display.clear(); obniz.display.print('Bring an umbrella!!'); } else if (await isMatched(YELLOW_WEATHERS, currentWeather)) { rgbled.rgb(255, 255, 0); obniz.display.clear(); obniz.display.print('Better bring an umbrella!'); } else if (await isMatched(GREEN_WEATHERS, currentWeather)) { rgbled.rgb(0, 255, 0); obniz.display.clear(); obniz.display.print('No need for umbrellas.'); } else { rgbled.rgb(0, 0, 0); console.log("no data"); } }, 30000); node-fetchのインストール フェッチAPI はNodeに実装されていません。 そのためnode-fetch のように外部モジュールを使う必要があります。 下記をターミナルでNodeアプリケーションにインストールする必要があります。 npm i node-fetch --save フェッチAPIを使用しているファイルの先頭に、以下の行を追加します。 const fetch = require("node-fetch"); 最後に obnizをしばらく、玄関において出勤前の確認に使いたいと思います! これできっと、1階まで下りてから部屋に戻ることも減るはず...!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

obnizで天気予報を可視化して傘予報をしてみた☂

最近、家を出てから雨を降っていることに気づき部屋までちょいちょいあり、どうにかならないかと思い作ってみました! 概要図 傘不要(晴れ)、折り畳みを持って行った方がいい場合(曇り)、傘必要(雨)の場合に分けています。 それぞれ緑・黄色・赤で表現しています。 ■コード全文 node.js const fetch = require("node-fetch"); const APIKEY = "APIKYEを入力"; const RED_WEATHERS = [ "Rain", "Snow", "Thunderstorm", "Drizzle", "Fog", "Squall" ]; const YELLOW_WEATHERS = [ "Clouds", "Mist", "Smoke", "Dust", "Haze", "Sand", "Ash", "Tornado" ]; const GREEN_WEATHERS = ["Clear"]; const Obniz = require('obniz'); const obniz = new Obniz('Obniz_ID'); // Obniz_IDに自分のIDを入れます obniz.onconnect = async function () { // RGB LEDを利用 const rgbled = obniz.wired('WS2811', { gnd: 0, vcc: 1, din: 2 }); // ディスプレイ表示(初期画面) obniz.display.clear(); obniz.display.print('Hello obniz!'); obniz.repeat(async () => { //現在の天気データ呼び出し let data = await (await fetch( `http://api.openweathermap.org/data/2.5/weather?q=Hokkaido,jp&appid=${APIKEY}` )).json(); console.log(data); let currentWeather = data.weather[0].main; if (currentWeather === undefined || currentWeather === null) { rgbled.rgb(0, 0, 0); return; } if (await isMatched(RED_WEATHERS, currentWeather)) { rgbled.rgb(255, 0, 0); obniz.display.clear(); obniz.display.print('Bring an umbrella!!'); } else if (await isMatched(YELLOW_WEATHERS, currentWeather)) { rgbled.rgb(255, 255, 0); obniz.display.clear(); obniz.display.print('Better bring an umbrella!'); } else if (await isMatched(GREEN_WEATHERS, currentWeather)) { rgbled.rgb(0, 255, 0); obniz.display.clear(); obniz.display.print('No need for umbrellas.'); } else { rgbled.rgb(0, 0, 0); console.log("no data"); } }, 30000); }; async function isMatched(array, _currentWeather) { for await (let weatherName of array) { if (_currentWeather === weatherName) { return true; } } return false; } 使用したもの ・obniz Board 1Y ・フルカラーLED APIの準備 OpenWeatherのAPIのページで、sign upをクリックし、必要事項を記入してアカウントを作成します。 APIを叩いて帰ってくる天気をグループに分けます。 const RED_WEATHERS = [ "Rain", "Snow", "Thunderstorm", "Drizzle", "Fog", "Squall" ]; const YELLOW_WEATHERS = [ "Clouds", "Mist", "Smoke", "Dust", "Haze", "Sand", "Ash", "Tornado" ]; const GREEN_WEATHERS = ["Clear"]; 30秒毎に結果を取得し、ディスプレイ表示とLEDの色を設定します。 今回は東京の天気で設定しています。 obniz.repeat(async () => { //現在の天気データ呼び出し let data = await (await fetch( `http://api.openweathermap.org/data/2.5/weather?q=Hokkaido,jp&appid=${APIKEY}` )).json(); console.log(data); let currentWeather = data.weather[0].main; if (currentWeather === undefined || currentWeather === null) { rgbled.rgb(0, 0, 0); return; } if (await isMatched(RED_WEATHERS, currentWeather)) { rgbled.rgb(255, 0, 0); obniz.display.clear(); obniz.display.print('Bring an umbrella!!'); } else if (await isMatched(YELLOW_WEATHERS, currentWeather)) { rgbled.rgb(255, 255, 0); obniz.display.clear(); obniz.display.print('Better bring an umbrella!'); } else if (await isMatched(GREEN_WEATHERS, currentWeather)) { rgbled.rgb(0, 255, 0); obniz.display.clear(); obniz.display.print('No need for umbrellas.'); } else { rgbled.rgb(0, 0, 0); console.log("no data"); } }, 30000); node-fetchのインストール フェッチAPI はNodeに実装されていません。 そのためnode-fetch のように外部モジュールを使う必要があります。 下記をターミナルでNodeアプリケーションにインストールする必要があります。 npm i node-fetch --save フェッチAPIを使用しているファイルの先頭に、以下の行を追加します。 const fetch = require("node-fetch"); 最後に obnizをしばらく、玄関において出勤前の確認に使いたいと思います! これできっと、1階まで下りてから部屋に戻ることも減るはず...!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript]プリミティブ型・オブジェクト型の違い

目的 めも プリミティブとオブジェクトとラッパーについてうまく説明できない人向け プリミティブ型とオブジェクト型 型 値 プリミティブ 数値・文字列・真偽値・undifined・null オブジェクト それ以外 大きな違い オブジェクトには、関数とプロパティが存在する。 プリミティブにはない。 疑問①文字列に対して使われるlengthはプロパティでは?? lengthは、ラッパーオブジェクトのプロパティ。 プリミティブ型に対して使うと、自動でプリミティブ型からオブジェクト型に変換されて(ラップされる)ラッパーオブジェクトになるため使用できる。 ラッパーオブジェクトとは 本来プリミティブ型である数値や文字列を、オブジェクトのようにして使えるようにしたもの。 let stringObj = new String("Hello"); let numberObj = new Number(0); 参考記事 プリミティブとオブジェクトとラッパーオブジェクト プリミティブ型とオブジェクト型
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[obniz]季節の変わり目に飲み物提供マシン

ここだけ見てくれたら閉じていいよw 今回はこんなものを作りました! この動画見てくれたら、もうそれでいいですw 気温判断で飲み物提供マシンを作りました!寒いと温かい飲み物を暑いと冷たいものを提供してくれます!荒々しすぎるしコード邪魔で奇跡の一枚になりそう笑#protoout #obniz pic.twitter.com/1v20lCHRRU— kkyosuke (@kkyosuke17) May 16, 2021 見てくださりありがとうございました。 以下このマシンの概要になるので気になる方はぜひご覧ください。 暑いし寒い、1日の気温の変化が大変 この時期、朝は寒いのに昼間は暑い。。 日が暮れるころにはまた寒いなんて感じませんか? そんなときに少し役に立つ飲み物の提供マシンをobnizでつくりました。 マシンの中身 まず温度センサー(LM60 | JS Parts Library | obniz)とLEDWS2811 | JS Parts Library | obnizが使用されています。 温度センサーで気温を計測し、設定した温度より高いか低いかでLEDが変色します。 今回のマシンに欠かせないのがサーボモータ!!(サーボモーターの概要) 回転する鍋蓋と土台のボールの間に設置しました。 これを固定してバランスとるのが難しかったw あとは回転する鍋蓋の上につけられたカップに飲み物の温度を保つための保冷剤とカイロを入れて完成! ここも見る価値ありかも!obnizの配線とソースコード 各部品とobnizは以下のように接続しました。 使用したobnizは1Yボードになります。 以下が使用したソースコードになります。 const Obniz = require('obniz'); //IDの書き換え忘れずに!! const obniz = new Obniz('ID'); obniz.onconnect = async () => { // 温度センサ const tempsens = obniz.wired('LM60', { gnd: 0, output: 1, vcc: 2 }); // RGB LEDを利用 const rgbled = obniz.wired('WS2811', { gnd: 4, vcc: 5, din: 6 }); // サーボモータを利用 const servo = obniz.wired('ServoMotor', { gnd: 9, vcc: 10, signal: 11 }); // 角度を保持する変数 let degrees = 90.0; // obnizディスプレイ(初期表示) obniz.display.clear(); obniz.display.print('obniz Ready'); // setIntervalで定期実行 setInterval(async () => { // 同期的に取得 const temp = await tempsens.getWait(); // 温度をコンソールに表示 console.log(temp + 'C'); // 温度をobnizディスプレイに表示 obniz.display.clear(); obniz.display.print(temp + 'C'); // 温度によって判定 if (temp < 22.0) { //冷たい飲み物が置いてあるほうに回転 degrees = 180.0; servo.angle(degrees); //水色のLEDを点灯 rgbled.rgb(45, 140, 255); } else { //暖かい飲み物が置いてあるほうに回転 degrees = 1.0; servo.angle(degrees); //オレンジ色のLEDを点灯 rgbled.rgb(255, 110, 30); } }, 180000); // 180000ミリ秒 = 30分ごとに実行 }; できなかったこと・・・ ここまで完成はしたのですが本来使いたかったのはこの部品! 圧力センサー(FSR-40X)!!!! こいつを使って飲み物が減っているかを検知したかった。 もし30分以上減っていなければ音を鳴らし脱水症防止にも役立てられただろうに・・・ なぜ実装に至らなかったかというと短すぎwwwww 回転盤の素材や土台をもうすこし考えてから再挑戦したと思います! まとめ 今回はあったらいいなでいうと1日の水分摂取量を管理してくれるものが作りたかったのでできなかったことのほうがあったらいいなでした。 しかし今回作成したマシンを見てこれは面白いし、こちらを作りたいという気持ちが勝ったため圧力センサーは今回外しました。 作成前はこのマシンはあまり面白くないだろうなと思い圧力センサーを思いついたのですが動いているところを見たらシュールで面白かったです。 やはり作ってみるまで分からないところもあるなと思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Youtubeの広告を簡単にスキップするChrome拡張 - YoutubeAdSkipper

開発の動機 Youtubeをながら見しているとき、表示される広告をスキップするのが面倒くさくなるときありませんか? 私は、いちいちマウスを操作してクリックするのが面倒くさくて、せめてキーボードショートカットでも、と思って、いろいろ探してみたんですが、Chrome拡張にも見当たらず、いっそのこと作ってみるかというのが始まりです。ショートカットキーだけでは寂しいので、自動でスキップする機能も実装することにしました。 機能 Youtube playerに表示される広告を自動でスキップします。 手動で直ちに広告をスキップするショートカットキーを追加します。 インストール方法 YoutubeAdSkipperはまだパッケージ化していません。githubのレポジトリから直接ダウンロードして、インストールしてください。 1. 以下のレポジトリにアクセスして、ZIPファイルをダウンロードします。 https://github.com/oyajizap/YoutubeAdSkipper 2. ダウンロードしたZIPファイルを、ローカルストレージの適当なフォルダに解凍します。 3. Chromeの拡張機能設定画面を開き(アドレスバーに"chrome://extensions"と入力します)、デベロッパーモードを有効にします。 4. 「パッケージ化されていない拡張機能を読み込む」をクリックし、先ほど解凍したフォルダを選択します。 必須ではありませんが、拡張機能のアイコンをクリックして、本拡張機能のアイコンを「固定」しておくことをお勧めします。 使用方法 auto skipモードが有効の間は、スキップ可能な広告が表示されてから指定された時間の経過後、自動でスキップします。また、以下のショートカットキーが使用可能です。 's' キー :: auto/manualモードの両方で、直ちに広告をスキップします。 'Shift+s'キー :: auto/manualモードをトグルします。モードのトグルは、マウス右クリックで表示されるコンテキストメニューからも可能です。 デフォルトのモードと、自動スキップでのタイマーは、本拡張の設定画面で変更可能です。 注意事項 この拡張は、Youtube.comでしか機能しません。ほかのサイト上での、組み込みYoutubeプレーヤーでは機能しません。 動画の最初にアンケート形式の広告が表示されることがありますが、その形式はサポートしていません。 本拡張のアイコンの作成には、以下のサイトを利用して作成させていただきました。なかなかおしゃれなアイコンを、解像度や色を自由自在に変更して作成できるので、とても使いやすいです。 その他もろもろ Youtubeの広告による収益モデルを尊重して、あえて一定期間は広告を表示するようにしています。もっと短いほうが良い方は、設定でタイマーの値を変更してください。 Chromeウェブストアへの公開は現在保留中です。ある程度需要があるようなら公開したいと思っています(デベロッパー登録料は大した金額ではないんですが、お金払って誰も使ってくれないと悲しいので…)。 何分にもChrome拡張を作るのも初めてなら、JavaScript自体もほぼ初めて触る感じなので、お見苦しい点もあるかもしれませんが、なにとぞご容赦ください。要望や改善点など教えていただければ大変助かります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue2】 Vue Routerの機能の整理

Vue Routerを使う インストール npm i vue-router vue-routerの設定を行う(ルーティングとコンポーネントの紐づけ) ( /src/router.js ) import Vue from 'vue'; import Router from 'vue-router' import Home from '/views/Home.vue' import About from '/views/About.vue' Vue.use(Router) // プラグイン(どこでも使える機能)を適用するために記載 export default new Router({ routes:[ { path:'/', // URL component:'Home' // 上記URLのときに表示するコンポーネント }, { path: '/about', component:'About' } ] }) routerをVueのグロバルコンポーネントで適用する( /src/main.js ) import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount("app") 表示個所にrouter-viewを用意する (/src/App.vue) <template> <div> <router-view></router-view> </div> </template> さまざまな機能 URLの#を消す(mode:history) デフォルトはmode:hashで、#がついている。mode: history とすることで、URLの#を消すことができる。 ( /src/router.js 一部抜粋) export default new Router({ mode: 'history', routes:[ ... ] }) URLを切り替える (router-link to="/") <router-link to="リンク先">とすることで、指定したリンク先に遷移するリンクができる (/src/App.vue) <template> <div> <router-link to="/"> HOME </router-link> <router-link to="/about"> ABOUT </router-link> <router-view></router-view> </div> </template> to属性はv-bindでバインディングできる。 URLに応じてクラスを変える (active-class) active-class="クラス名"をすることでリンクを含むURLの時のみクラスが適用される。exactをつけることで完全一致するURLの時のみ適用されるクラスを設定できる。 (/src/App.vue) <template> <div> <router-link to="/" active-class="active" exact> HOME </router-link> <router-link to="/about" active-class="active"> ABOUT </router-link> <router-view></router-view> </div> </template> <style scoped> .active{ font-size: 30px; } </style> URLをJavaScriptから変更する($router.push) this.$router.push({path: '遷移先URL'})とすることで、JavaScriptから遷移させることができる(/src/App.vue) <template> <div> <button @click="toAbout">to About</button> <router-view></router-view> </div> </template> <script> export default { methods: { toAbout() { this.$router.push({path: 'about'}) } } } </script> 動的なURL(path: /xxx/:yyy, $route.params) path '/xxx/:yyy'のように、コロンを使ってパラメータ(yyy)を指定することで、動的なURLができる (/src/router.js 一部抜粋) export default new Router({ routes:[ { path:'/', component:'Home' }, { path: '/about/:id', // パラメータidを指定 component:'About' } ] }) $route.params.idで動的なURLのパラメータにアクセスできる (/src/views/About.vue) <template> <div> <!-- --> <p>No. {{$route.params.id}}</p> </div> </template> ※ 動的なURLはライフサイクルフックが呼ばれないので、URLの変更で何か処理をさせたい場合は、watchで$routeを監視する。 動的なURLのパラメータをpropsからアクセス (props: true) props: trueにすることで、動的パラメータをpropsでアクセスできる (/src/router.js 一部抜粋) export default new Router({ routes:[ { path:'/', component:'Home' }, { path: '/about/:id', component:'About', props: true // propsを追加 } ] }) $route.params.idで動的なURLのパラメータにアクセスできる (/src/views/About.vue) <template> <div> <p>ID: {{id}}</p> </div> </template> <script> export default{ props: ['id'] } </script> ※ 動的なURLはライフサイクルフックが呼ばれないので、URLの変更で何か処理をさせたい場合は、watchで$routeを監視する。 router-view のなかにrouter-view (children) childrenを追加することでネストされたrouter-viewを実現できる (/src/router.js) import Vue from 'vue'; import Router from 'vue-router' import Home from '/views/Home.vue' import About from '/views/About.vue' import AboutChild from '/views/AboutChild.vue' Vue.use(Router) export default new Router({ routes:[ { path:'/', component:'Home' }, { path: '/about', component:'About', children:: [ // childrenオプションでpath, componentを設定する {path: "child", component: AboutChild} // …/about/childでアクセス可 ] } ] }) 名前付きのルートを使う (name) nameを使ってルートに名前を設定する (/src/router.js 一部抜粋) export default new Router({ routes:[ { path: '/about:id', component:'About', children:: [{ path: "child", component: AboutChild, name: 'name-child' // ルートの名前を設定 }] } ] }) nameで指定したルートの名前を渡す。パラメータはparamsでオブジェクトで指定する (/src/views/App.js) <template> <div> <router-link :to= "{name:'name-child', params:{id:1}}"> ABOUT </router-link> <router-view></router-view> </div> </template> クエリにアクセス (query) (/src/router.js 一部抜粋) export default new Router({ routes:[ { path: '/about', component:'About', name: 'about' // 名前付きルートを設定 } ] }) クリックすると/about?id=1というURLになる (/src/App.vue) <template> <div> <!-- クリックすると /about?id=1&page=2というURLになる--> <router-link :to= "{name:'about', query:{id:1, page:2}}"> ABOUT </router-link> <!-- こちらでも可能 --> <router-link to="/about?id=1&page=2"></router-link> <router-view></router-view> </div> </template> クエリはthis.$route.query.idで取得できる。 リダイレクト機能 (redirect) redirectを使うことで、リダイレクトを実装できる (/src/router.js 一部抜粋) export default new Router({ routes:[ { path: '/about', component:'About' }, { path:'*', // アスタリスクを指定することで任意のURLという意味になる redirect:'/' // 遷移先のURLを指定 } ] }) router-viewにトランジション (transition) router-viewにもトランジションを適用させることができる (/src/App.vue) <template> <div> <transitin name="fade" mode="out-in"> <router-view></router-view> </transitin> </div> </template> <style scoped> .fade-enter, .fade-leave-to { opacity: 0; } .fade-enter-active, .fade-leave-active{ transition: opacity .5s } </style> # idを指定する (hash) クリックすると/about#nextというURLになる (/src/App.vue) <template> <div> <!-- クリックすると /about#nextというURLになる--> <router-link :to= "{name:'about', hash: 'next'}"> ABOUT </router-link> <!-- こちらでも可能 --> <router-link to="/about#next"></router-link> <router-view></router-view> </div> </template> 指定IDにスクロールさせる (scrollBehavior) トランジションがない場合のみ使用できる。これは、transitionがあると、#nextが発生する前にscrollBehaviorが起動するためである (/src/router.js 一部抜粋) export default new Router({ routes:[ { path:'/', component:'Home' } ], scrollBehavior(to, from, savedPosition) { // 前回の位置に戻る if(savedPosition) { return savedPosition } // URLにhashが含んでいたらハッシュの位置まで移動 if(to.hash) { return { selector: to.hash } } // ページの先頭に移動 return {x: 0, y: 0} } }) すべてのページの遷移時に実行する (beforeEach) /src/main.js import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false // to:$route(次の遷移先の状態) from:$route(前の状態) next:次の遷移先に進む関数 router.beforeEach((to, from, next) => { console.log('before each!') // 引数も取れる // next(false):次に遷移させない // next('/'):'/'に遷移 next() }) new Vue({ router, render: h => h(App) }).$mount("#app") 特定のページの遷移時に実行する (beforeEnter) boforeEnterを使う (/src/router.js 一部抜粋) export default new Router({ routes:[ { path: '/about', component:'About', beforeEnter(to, from, next) { console.log('before enter!') // 引数も取れる // next(false):次に遷移させない // next('/'):'/'に遷移 next() } }, ] }) ナビゲーションガードをコンポーネントに指定 Vueコンポーネントに記載する <script> export default { beforeRouteEnter(to, from, next) { // コンポーネントを描画するルートが確立する前 next() }, beforeRouteUpdate(to, from, next) { // コンポーネントを描画するルートが変更されたとき next() }, boforeRouteLeave(to, from, next) { // コンポーネント終了後(ナビゲーションから離れていく時) const isLeave = window.confirm("このページを離れますか?") if(isLeave) { next() }else { next(false) } } } </script> 遅延ローディング (const xx = () => import("./xxx.vue")) router.jsのimport分の記載方法を変えることで遅延ローディング(後からjavascriptを取得する)が可能 (router.jsの一部を抜粋) // ● 今までの書き方 // import Home from './views/Home.vue' // ● 遅延ローディングの場合 const Home = () => import(/* webpackChunkName: "Home" */ "./views/Home.vue") 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

obnizでわが子と一緒に「オリジナル目覚ましBot」を作って、実際に起きられるかどうかを試した結果

今回の目的 ~わが子に"オリジナル目覚まし"を作りたい!~ 来年の今頃は、わが家の息子は小学校1年生。 今後のために、朝は1人で起きられるようになって欲しい!! そんな思いから、先日学習したobnizで「オリジナル目覚ましBot」を作ってみよう!と考えました。 そもそも、わが家の息子は、朝がとっても苦手。 毎日、必死に起こすのが日課となっていますが、寝起きがとっても不機嫌です? 本人いわく、「パパやママの声だと、ビックリするから嫌」なのだとか。 その後の支度もグダグダとなってしまうので、なんとか笑顔で朝を迎えてくれたら・・・と、模索しており、このあたりもBotで解消できればと思います。 完成品(「わが子専用 目覚ましBot」) 完成品のご紹介です。 LINEBotからの合図とともに、obniz電圧スピーカーからメロディーが流れる仕様。 前回の記事で作ったLINEBotを、一部アレンジしています。 わが子専用「オリジナル目覚ましBot」が完成!?リズムが所々不自然ですが、個人的には苦労した部分なので、かえって愛着が湧いてます。笑さて、わが子は起きられるようになるのでしょうか??#obniz #LINEBot #子ども#ポケモン #めざまし #Javascript pic.twitter.com/ZPyClWlFi3— くるみ (@Kokano23) May 16, 2021 メロディーは、「めざせ ポケモンマスター」。 息子本人のモチベーションがなにより大事になると思ったので、「どんな曲なら起きられそう?」と本人にヒアリングして一緒に決めました。 前回作ったピカチュウBotが好評で、「今度は、ミュウに起こして欲しい!」と要望もありました。幻のポケモン"ミュウ"が、今は一番好きなのだそう。 そこで、今回もLINEのリッチメニューを活用しました。(ポケモンの画像は、pokeAPIより取得) ミュウをタッチすると、テーマソングの再生とともに応援メッセージが届く仕組みです。実際は、わが子の名前も入れて、オリジナル感を演出しました。 (これを見れば、きっと笑顔になれる!) 作り方 ①obnizの設定 準備したもの obniz Board 1Y 圧電スピーカー(圧電サウンダ)(13mm)PKM13EPYH4000-A0 事前設定や接続は、obniz公式ドキュメント(起動とWi-Fi設定)やobniz公式ドキュメント(Speaker)を参考にしました。 ②LINEBotの準備 Botアカウントの準備は、前回と同様に、こちらの記事を参考にしています。 ③メロディーの設定 音階周波数を参考にしました。 「めざせポケモンマスター」の楽譜から、メロディーをソースコードに組み込みました。 リズムが不自然になってしまったところがあり、手作り感が満載です。(テンポの調整など、何度も再生しながら試行錯誤しました。難しかったです。) ソースコード ソースコード全文です。(セクション内参照) 'use strict'; // obniz呼び出し const Obniz = require('obniz'); const obniz = new Obniz("XXXXXXXXX"); // Obniz_ID に自分のIDを入れます const express = require('express'); const line = require('@line/bot-sdk'); const PORT = process.env.PORT || 3000; // Messaging APIで利用するクレデンシャル(秘匿情報)です。 const config = { channelSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', channelAccessToken: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' }; const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい) console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); const client = new line.Client(config); // obniz接続 obniz.onconnect = async function () { obniz.display.clear(); obniz.display.print("obniz meets LINE Bot!"); } function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } let mes = '' if(event.message.text === 'めざましかけて'){ mes = 'すこしまってね'; //メッセージだけ先に処理 getAskObnizSpeaker(event.source.userId); //スクレイピング処理が終わったらプッシュメッセージ }else{ mes = event.message.text; } return client.replyMessage(event.replyToken, { type: 'text', text: mes }); } const getAskObnizSpeaker = async (userId) => { // スピーカーを呼び出す const speaker = obniz.wired("Speaker", {signal:0, gnd:1}); console.log("Speaker") await client.pushMessage(userId, { type: 'text', text: "おはよう!きょうもがんばろう!", }); // 音階とhzの参考サイト https://tomari.org/main/java/oto.html // 「めざせポケモンマスター」 await speaker.play(880.000); // A5 ラ await obniz.wait(800); await speaker.stop(); await obniz.wait(80); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(880.000); // A5 ラ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(783.991); // G5 ソ await obniz.wait(1200); await speaker.stop(); await obniz.wait(500); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(80); await speaker.play(880.000); // A5 ラ await obniz.wait(150); await speaker.stop(); await obniz.wait(80); await speaker.play(880.000); // A5 ラ await obniz.wait(150); await speaker.stop(); await obniz.wait(80); await speaker.play(880.000); // A5 ラ await obniz.wait(150); await speaker.stop(); await obniz.wait(80); await speaker.play(880.000); // A5 ラ await obniz.wait(140); await speaker.stop(); await obniz.wait(80); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(80); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(80); await speaker.play(880.000); // A5 ラ await obniz.wait(100); await speaker.stop(); await obniz.wait(80); await speaker.play(783.991); // G5 ソ await obniz.wait(1200); await speaker.stop(); await obniz.wait(600); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(880.000); // A5 ラ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(880.000); // A5 ラ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(783.991); // G5 ソ await obniz.wait(900); await speaker.stop(); await obniz.wait(300); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(783.991); // G5 ソ await obniz.wait(140); await speaker.stop(); await obniz.wait(60); await speaker.play(659.255); // E5 ミ await obniz.wait(150); await speaker.stop(); await obniz.wait(80); await speaker.play(783.991); // G5 ソ await obniz.wait(900); await speaker.stop(); await obniz.wait(300); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(880.000); // A5 ラ await obniz.wait(140); await speaker.stop(); await obniz.wait(60); await speaker.play(783.991); // G5 ソ await obniz.wait(150); await speaker.stop(); await obniz.wait(60); await speaker.play(880.000); // A5 ラ await obniz.wait(140); await speaker.stop(); await obniz.wait(60); await speaker.play(783.991); // G5 ソ await obniz.wait(600); await speaker.stop(); await obniz.wait(80); await speaker.play(1046.502); // C6 ド await obniz.wait(200); await speaker.stop(); await obniz.wait(80); await speaker.play(987.767); // B5 シ await obniz.wait(1200); await speaker.stop(); await obniz.wait(80); speaker.stop(); } app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); 結果 ~メロディー再生では起きず。でも、笑顔は達成!~ さっそく「目覚ましBot」を使ってみた結果、メロディー再生ではピクリとも動かず、いつも通りに名前を連呼しながら起こすことになりました。(わが子、手強いです) 実際に使ってみると、目覚ましの割にはボリュームが小さい点など、さっそく改善したい部分が見つかりました。 一方、寝起きには「これ、大好きな歌だ!」と反応していて、ミュウの画像やひらがなのメッセージを読んで、笑顔で目覚めることができました。ここは狙いどおりです! メロディーに慣れてきた場合、飽きてしまいそうなので、子どもが好きな曲のランダム再生などの機能が追加できたら面白そう・・・と新たな妄想が膨らみました。 今後は、一定時間になったら起動するタイマーやスヌーズ機能なども付けてみたいと思っています。 参考 ▶LINEでメッセージを送ると メロディを奏でるBOT【IoT連携】【LINE BOT】 ▶1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超リアルなザクを目指して!obnizでぐぽーんと音がしてモノアイが光るプロトタイプを作成!!

ぐぽーんと音がしてモノアイが光るのは男のロマン ガンダムの色んな作品でよく見かける演出として、 格納庫でスタンバってるMS(モビルスーツ)の目が「ぐぽーん」という音と共に光る というのがあります。(ザクやドムなどバリエーションも豊富) この演出、個人的に大好物なんですが、いつかこれをガンプラで再現したいなーと思っています。 今回は100均で見かけたおもちゃとIoT基盤「obniz」を組み合わせてプロトタイプを作ったので記事にしていきます。 完成品(ぐぽーんとなってモノアイ光るぜ) 実際に作ったものはこんな感じで動きます。 できた!(スピーカーのノイズが凄いとかLEDの赤がカメラ通すとピンクに見えるとかあるけど気にしない)#obniz pic.twitter.com/sQLUUQqyrz— shoito66 (@shoito66) May 15, 2021 作り方 用意する(した)もの ハードウェア ■obniz Board 1Y  制御の要となります。 ■赤色LED  抵抗内蔵のもの。モノアイに見立てて使います。 ■ブレッドボード  配線用。そのままだとLEDからobnizへの配線が厳しかったため。 ■Grove_MP3  グポーンという音を出すための装置。obnizから命令を送るとMP3ファイルを再生してくれます。 ■スピーカー  Grove_MP3に繋げて音を出してもらいます。今回はダイソーで300円のものを購入。 ■Keyestudio_Button  音と光を制御するためのボタン。INPUTになれば良いのでセンサーの類でも代用できます。 ■プチブロック ロボットシリーズ  ザクの代わりにするべくダイソーで購入。今回はスナイプをベースにナイトのパーツを少し混ぜてます。 ■適当な箱  作ったロボットと各種機器を固定するためのもの。ダイソーで購入。 ソフトウェア ■Node.js  obnizの制御に使いました。 組み立て ロボットはこんな感じで組み立てました。(LEDをモノアイ代わりにするので顔だけちょっと改造しています) 箱に穴をあけてブレッドボードと赤色LEDを取り付けます。 配線は穴を通して裏側へ。 裏側はこんな感じになってます。 (黒く塗りつぶしているのはobniz-IDを隠すためです。) 後は、LEDの位置に顔が来るようにロボットを配置して完了! 制御用プログラムを実行してボタンを押せばぐぽーんという音とともにモノアイが光ります! 制御用プログラム obnizの制御用コードは以下の通りです。 起動後、ボタンを押すとMP3の再生とLEDの点灯処理が実行されます。 (LEDは3秒後に消えるようにしています。) const Obniz = require('obniz'); const obniz = new Obniz('XXXX-XXXX'); // 自分のObniz_ID const sleep = (msec) => new Promise(res => setTimeout(res, msec)); obniz.onconnect = async function () { // MP3プレーヤーを利用 const mp3 = obniz.wired("Grove_MP3", { gnd: 0, vcc: 1, mp3_rx: 2, mp3_tx: 3 }); await mp3.initWait(); mp3.setVolume(20); // 赤色LEDを利用 const led = obniz.wired("LED", { anode: 5, cathode: 6 }); // 入力用にボタンを利用する const button = obniz.wired("Keyestudio_Button", {signal:9, vcc:10, gnd:11}); // ディスプレイ obniz.display.clear(); // クリア obniz.display.print('Ready!'); // ボタンが押されたら処理 button.onchange = async function (pressed) { if (!button.isPressed) { //ぐぽーんという音を再生 mp3.play(1); ///モノアイを点灯 led.on(); //3秒経ったらLEDをオフにする。 await sleep(3000); led.off(); } } } 補足(Grove_MP3の使い方) 今回、どうしても「ぐぽーん」が聞きたかったのでobnizでMP3ファイルを再生できる機器を探したところ、 公式ドキュメントで紹介されていたGrove_MP3に行きつきました。 ただ、こいつを使うまでに色々あったので補足しておきます。 そのままではobnizに接続できない。 Grove_MP3は名前の通り、Grove規格のコネクタを持っています。 当然、付属のケーブルもGrove規格のものなのですが、obnizはGrove規格のソケットを持っていないのでそのままだと接続できません。 なので、ケーブルを別途購入しました。 GROVE - 4ピン - ジャンパオスケーブル(5本セット) 使用できるのはマイクロSDカード(8GB以下のもの) obniz公式にSDカードという記載がありますが、実際にはマイクロSDカードを使うことになります。 容量も8GB以下しか使えないのでご注意を。 使用する際はマイクロSDカードに「MP3」というフォルダを作って「0001.mp3」みたいに数字4桁を頭につけたファイル名のMP3ファイルを格納しておけば再生できます。 プログラムの書き方 プログラム自体は以下のような形で記述します。 ※音量は明示的に設定しないと最大値で再生されるので注意 const mp3 = obniz.wired("Grove_MP3", { gnd: 0, vcc: 1, mp3_rx: 2, mp3_tx: 3 }); await mp3.initWait();//初期化 mp3.setVolume(20);//音量設定(0~31)※デフォルトは31 mp3.play(1);//MP3フォルダ内の「0001」から始まるMP3ファイルを再生 最後に 今回のプロトタイプ作成ではソフトウェアではなくハードウェアに力を入れました。 初心者なりにカッコいいものができたと満足しております。 次は実際にザクのプラモデルにLEDを仕込んで「ぐぽーん」やりたいですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ヘッドレストと頭の距離を計測して顔を画面へ突き出したら音で教えるobnizで首コリ頭痛を防ごう

前回の整体通院から1週間とたたずにこめかみが締め付けるような頭痛がしはじめ、整体師さんから「首の上の筋肉まで固まってますね。」と首と頭の付け根に鍼を6本も打たれたので、次の2週間後の整体までに頭痛を起こさないように猫背になったら音で注意してくれるシステムを作りました。 実際に動いているところの動画 椅子のヘッドレストの上にobnizを設置して、距離センサーで頭との距離を計測して、その値によってビープ音を鳴らします。(動画のビープ音は小さめかもしれません) いつもの姿勢がセンサーから何㎝なのかを計測する 私の姿勢は下図のような感じですので、obnizからの計測値をconsoleに出しながら何㎝くらいが閾値として適正なのか見極めていきました。 改めて写真に撮ってみると頭(体重の10%)が重力に引かれるのに逆らう筋肉が姿勢によって異なりそう、というのがわかります。 席を立つときにOFF、席に戻ったらONは絶対運用しなくなる 当初想定していた「10㎝未満だと音が鳴らない。10㎝以上だと音が鳴る」という条件だと席から離れてもビープ音が鳴り続けてしまいます。「席を立つときにOFF、席に戻ったらONにする」という”運用でカバー”は絶対しなくなる自信があるので「50㎝以上だと音が鳴らない」というif文を追加しました。 物理法則には適わない。obnizが重力に負けて2回落ちる。 椅子がメッシュ生地なのでブレッドボードの粘着シールでは弱く、後傾姿勢になってから数分でモバイルバッテリーがobnizを巻き添えにして床に落ちました。それでモバイルバッテリーを落ちないように椅子に固定したのですがそれでもブレッドボードとobnizだけの重さで落ちました。(壊れなくて良かった…) 「魔法のテープ 極」という強力な両面テープを持っていたのでこれで固定しました。テグスがあればメッシュの隙間に通してブレッドボードを固定するのがいいかな、と思います。 意外と測定不能値が出るので1mm以上というif文を加える 当初はif文のelseでビープ音が鳴るようにしていたので計測不能値が出てもビープ音が鳴っていました。1秒単位で計測していると割と計測不能が出てしまうので「1mm以上を計測した場合はビープ音を鳴らす」というelse ifを追加して、elseでは音を鳴らさないようにしました。 環境 Node.js 15.12.0 npm 7.6.3 obniz Board 1Y 超音波距離センサー HC-SR04 圧電スピーカー(圧電サウンダ)(13mm)PKM13EPYH4000-A0 コード distance_beep.js const Obniz = require('obniz'); const obniz = new Obniz('【各自のobniz ID】'); obniz.onconnect = async () => { // 超音波距離センサを利用 const hcsr04 = obniz.wired('HC-SR04', { gnd: 3, echo: 2, trigger: 1, vcc: 0, }); // スピーカーを利用 const speaker = obniz.wired('Speaker', { signal: 10, gnd: 11 }); // obnizディスプレイ(初期表示) obniz.display.clear(); obniz.display.print('obniz Ready'); // setIntervalで定期実行 setInterval(async () => { // 距離を取得 let distance = await hcsr04.measureWait(); // 小数点以下がたくさんあるのでここでは整数にします distance = Math.floor(distance); // 距離をコンソールに表示 console.log(distance + ' mm'); // 距離をobnizディスプレイに表示 obniz.display.clear(); obniz.display.print(distance + ' mm'); // 距離によって判定 if(distance > 500.0){ // 50cm より大きい場合、席を離れたと判断する。 obniz.display.clear(); obniz.display.print('Away From Keyboard.'); console.log('Away From Keyboard.'); // 音を停止する speaker.stop(); } else if (distance < 100.0) { // 10cm 未満の場合、良い姿勢と判断する。 obniz.display.clear(); obniz.display.print('Good!!'); console.log('Good!'); // 音を停止する speaker.stop(); } else if (distance > 1.0){ // 10cm 以上 50cm未満の場合、悪い姿勢を判断する。 // 計測不可時に音が鳴るのを防ぐため、1mm以上を条件としている。 obniz.display.clear(); obniz.display.print('Bad Posture!!'); console.log('Bad Posture!!'); // 100mm = 10cm を超えるときは300Hzで音を鳴らす speaker.play(300); }else { // 測定不可エラーの場合の処理 obniz.display.clear(); obniz.display.print('Error'); console.log('Error'); // 音を停止する speaker.stop(); } }, 1000); // 1000ミリ秒 = 1秒ごとに実行 }; 参考URL 肩こりがヤバイ人集まれ!指圧師が教えるラクな作業環境【リモートワーク】 おわりに コードとしては難しくないので簡単に完成までもっていけると目論んでいたのですが「椅子がobnizを粘着しにくい生地だったので固定部材を探す」「甲高いビープ音が家族に不評なので低い周波数に変える」など普段のソフト開発では気にしないところにハードを組み合わせた開発の難しさがあって新鮮でした。ハードいじりは楽しいですね。 ━-━-━-━-━-━-━-━-━-━-━-━-━━-━-━-━-━-━-━-━-━ 2021年4月からプロトアウト(プロトタイプ+アウトプット)スタジオに参加して、技術を学んだり自身を深掘りして卒業制作=クラウドファンディングのテーマを決めたりしています。 金融系SEという安定稼働を最優先にガチガチに設計書を作ってバグは許さぬ、という世界で十数年やってきました。自分の性格としても独創的なアイディア出しは苦手で、決まったことを正確に効率的にこなすことが得意です。 そんな私が無事クラウドファンディングに辿り着いて成功できるのか、見守っていただけましたら幸いです。 〇情報発信 ・自身とテーマ深掘り的な記事 → note ・開発中のつぶやき → Twitter
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

obnizで猫背になったら音が鳴る椅子を作って首コリ頭痛を防ぐ

前回の整体通院から1週間とたたずにこめかみが締め付けるような頭痛がしはじめ、整体師さんから「首の上の筋肉まで固まってますね。」と首と頭の付け根に鍼を6本も打たれたので、次の2週間後の整体までに頭痛を起こさないように猫背になったら音で注意してくれるシステムを作りました。 実際に動いているところの動画 椅子のヘッドレストの上にobnizを設置して、距離センサーで頭との距離を計測して、その値によってビープ音を鳴らします。(動画のビープ音は小さめかもしれません) いつもの姿勢がセンサーから何㎝なのかを計測する 私の姿勢は下図のような感じですので、obnizからの計測値をconsoleに出しながら何㎝くらいが閾値として適正なのか見極めていきました。 改めて写真に撮ってみると頭(体重の10%)が重力に引かれるのに逆らう筋肉が姿勢によって異なりそう、というのがわかります。 席を立つときにOFF、席に戻ったらONは絶対運用しなくなる 当初想定していた「10㎝未満だと音が鳴らない。10㎝以上だと音が鳴る」という条件だと席から離れてもビープ音が鳴り続けてしまいます。「席を立つときにOFF、席に戻ったらONにする」という”運用でカバー”は絶対しなくなる自信があるので「50㎝以上だと音が鳴らない」というif文を追加しました。 物理法則には適わない。obnizが重力に負けて2回落ちる。 椅子がメッシュ生地なのでブレッドボードの粘着シールでは弱く、後傾姿勢になってから数分でモバイルバッテリーがobnizを巻き添えにして床に落ちました。それでモバイルバッテリーを落ちないように椅子に固定したのですがそれでもブレッドボードとobnizだけの重さで落ちました。(壊れなくて良かった…) 「魔法のテープ 極」という強力な両面テープを持っていたのでこれで固定しました。テグスがあればメッシュの隙間に通してブレッドボードを固定するのがいいかな、と思います。 意外と測定不能値が出るので1mm以上というif文を加える 当初はif文のelseでビープ音が鳴るようにしていたので計測不能値が出てもビープ音が鳴っていました。1秒単位で計測していると割と計測不能が出てしまうので「1mm以上を計測した場合はビープ音を鳴らす」というelse ifを追加して、elseでは音を鳴らさないようにしました。 環境 Node.js 15.12.0 npm 7.6.3 obniz Board 1Y 超音波距離センサー HC-SR04 圧電スピーカー(圧電サウンダ)(13mm)PKM13EPYH4000-A0 コード distance_beep.js const Obniz = require('obniz'); const obniz = new Obniz('【各自のobniz ID】'); obniz.onconnect = async () => { // 超音波距離センサを利用 const hcsr04 = obniz.wired('HC-SR04', { gnd: 3, echo: 2, trigger: 1, vcc: 0, }); // スピーカーを利用 const speaker = obniz.wired('Speaker', { signal: 10, gnd: 11 }); // obnizディスプレイ(初期表示) obniz.display.clear(); obniz.display.print('obniz Ready'); // setIntervalで定期実行 setInterval(async () => { // 距離を取得 let distance = await hcsr04.measureWait(); // 小数点以下がたくさんあるのでここでは整数にします distance = Math.floor(distance); // 距離をコンソールに表示 console.log(distance + ' mm'); // 距離をobnizディスプレイに表示 obniz.display.clear(); obniz.display.print(distance + ' mm'); // 距離によって判定 if(distance > 500.0){ // 50cm より大きい場合、席を離れたと判断する。 obniz.display.clear(); obniz.display.print('Away From Keyboard.'); console.log('Away From Keyboard.'); // 音を停止する speaker.stop(); } else if (distance < 100.0) { // 10cm 未満の場合、良い姿勢と判断する。 obniz.display.clear(); obniz.display.print('Good!!'); console.log('Good!'); // 音を停止する speaker.stop(); } else if (distance > 1.0){ // 10cm 以上 50cm未満の場合、悪い姿勢を判断する。 // 計測不可時に音が鳴るのを防ぐため、1mm以上を条件としている。 obniz.display.clear(); obniz.display.print('Bad Posture!!'); console.log('Bad Posture!!'); // 100mm = 10cm を超えるときは300Hzで音を鳴らす speaker.play(300); }else { // 測定不可エラーの場合の処理 obniz.display.clear(); obniz.display.print('Error'); console.log('Error'); // 音を停止する speaker.stop(); } }, 1000); // 1000ミリ秒 = 1秒ごとに実行 }; 参考URL 肩こりがヤバイ人集まれ!指圧師が教えるラクな作業環境【リモートワーク】 おわりに コードとしては難しくないので簡単に完成までもっていけると目論んでいたのですが「椅子がobnizを粘着しにくい生地だったので固定部材を探す」「甲高いビープ音が家族に不評なので低い周波数に変える」など普段のソフト開発では気にしないところにハードを組み合わせた開発の難しさがあって新鮮でした。ハードいじりは楽しいですね。 ━-━-━-━-━-━-━-━-━-━-━-━-━━-━-━-━-━-━-━-━-━ 2021年4月からプロトアウト(プロトタイプ+アウトプット)スタジオに参加して、技術を学んだり自身を深掘りして卒業制作=クラウドファンディングのテーマを決めたりしています。 金融系SEという安定稼働を最優先にガチガチに設計書を作ってバグは許さぬ、という世界で十数年やってきました。自分の性格としても独創的なアイディア出しは苦手で、決まったことを正確に効率的にこなすことが得意です。 そんな私が無事クラウドファンディングに辿り着いて成功できるのか、見守っていただけましたら幸いです。 〇情報発信 ・自身とテーマ深掘り的な記事 → note ・開発中のつぶやき → Twitter
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3 + CloudFront + CloudFront Functions で Basic 認証付きの WEB コンテンツ配信

はじめに S3 上に格納した WEB コンテンツを CloudFront で配信する際に Basic 認証で簡易なアクセス制限をかけたい、というのはよくあるユースケースだと思います。というわけで、2週間ほど前(2021年5月3日)にリリースされたばかりの「CloudFront Functions」でやってみました。 ちなみに、この CloudFront Functions ですが、CDN のエッジ側で簡易な処理を高速かつ安価に実行できるサービスです。これまでも Lambda(Lambda@Edge)を使って同じようなことができていましたが、より Viewer(クライアント)に近いところに関数を配置でき、高速かつ安価な処理が実現できるようになった、とのことです。 Amazon CloudFront が軽量エッジコンピューティング機能である CloudFront Functions を発表 Introducing CloudFront Functions – Run Your Code at the Edge with Low Latency at Any Scale | AWS News Blog やったこと S3 をストレージ、CloudFront を CDN として用いた WEB コンテンツ配信において、エッジに配置する CloudFront Functions で Basic 認証の処理を実行します。 構成全体の設定手順 設定手順は大雑把には以下のとおりです。 独自ドメインで配信する場合、Certificate Manager で対象ドメインの SSL 証明書を用意 CloudFront で使うためには、us-east-1(「バージニア北部」リージョン) で証明書を作っておく必要あり 配信用の S3 バケットを用意して、WEB コンテンツを格納 バケットへのパブリックアクセスは「すべてブロック」 バケットのプロパティで「静的ウェブサイトホスティング」を有効化 CloudFront で Distribution を作成 「Origin Domain Name」に、作成した S3 バケットの ARN を設定 S3 バケットを us-east-1 以外のリージョンに作った場合、対象バケットの ARN には「<バケット名>.s3.<リージョン名>.amazonaws.com」と、リージョン名も含めるのがオススメ1 独自ドメインで配信する場合、「Alternate Domain Names」にドメイン名を追加し、作成しておいた SSL 証明書を指定 「Update Bucket Policy」にチェックを入れ、CloudFront に 対象 S3 バケットへの読み取り権限を付与 CloudFront で Basic 認証を処理するための Function を作成し、作成した Distribution に設定 CloudFront の 管理コンソールに新たに設けられた「Functions」で、関数を作成 作成した関数の「Associate」画面で、Basic 認証を適用したい Distribution を設定(複数設定可能) 作成した関数の「Publish」画面で、Publish(and update) Basic 認証を処理するための CloudFront Function ここでは、肝になる関数のコードのみ示します。 basic-auth-test function handler(event) { var request = event.request; var headers = request.headers; var authUser = 'username'; var authPass = 'password'; var authString = 'Basic ' + (authUser + ':' + authPass).toString('base64'); if (typeof headers.authorization === 'undefined' || headers.authorization.value !== authString) { var response = { statusCode: 401, statusDescription: 'Unauthorized', headers: { 'www-authenticate': {value: 'Basic'} } }; return response; } return request; } Lambda@Edge でサポートされている Node.js ではなく、CloudFront Functions では「ECMAScript 5.1 に準拠した JavaScript」を使って関数を定義する必要があります。今回は、よく見かける Lambda@Edge(Node.js)で Basic 認証するためのコード2をベースに、CloudFront Functions で動くよう仕立ててみました。 おわりに CloudFront Functions は、クライアントに近いところで高速に実行できて費用も低コストに抑えられる反面、最大実行時間は 1ms と制限が厳し目です。重い処理をさせることはできない3のですが、公式サンプル4でも、HTTP リクエスト/レスポンスヘッダーの加工、URLのリダイレクトやリライト、リクエスト認証などの例が示されており、工夫次第で有効に活用できるユースケースはたくさんありそうです。 「Amazon S3 での HTTP 307 エラーのトラブルシューティング」にあるとおり、S3 のエンドポイント情報の他リージョンへの浸透に時間がかかることへの対策です。 ↩ 例えば、「Amazon CloudFrontとAWS Lambda@EdgeでSPAのBasic認証をやってみる | DevelopersIO」にあるものなど。 ↩ 今回の Basic 認証のコードでも最大実行時間の 20〜30% 程度の実行時間がかかってます。 ↩ 「Example code for CloudFront Functions - Amazon CloudFront」「GitHub - aws-samples/amazon-cloudfront-functions」 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

正規表現を用いたreplaceメソッドの使い方

※プログラミング学習中の私がアップロードしてます。理解が浅いです。 この記事は厳密な仕様に関するものではなく、考え方理解のまとめとして受け取ってください。 正規表現を用いたreplaceメソッドの使い方 replaceメソッドにおける正規表現では、検索する文字列を『/(スラッシュ)』で囲み、末尾にフラグを付与します。 オプションのフラグを幾つかを列挙します。 フラグ フラグの意味合い g グローバルマッチ i 大文字と小文字の違いを無視する m 複数行を越えたマッチ 文字 説明 正規表現の例 マッチする例 ^ 直後の文字が行の先頭にある場合にマッチします。 ^google google... $ 直前の文字が行の末尾にある場合にマッチします。 google$ ...google つまり直後の文字で始まるってこと testメソッドとは testメソッドは文字列を正規表現でチェックするために使います。 指定した文字列が正規表現のパターンにマッチすれば「true」を返し、マッチしなければ「false」を返します。 testメソッドの使い方 ここでは、「test」を使った検索方法を解説します。 testは正規表現のパターンから呼び出し、引数にチェックする文字列を指定します。 このJavascriptの表現はパスの中にitemsがあり、transactionsが含まれておりparamsの中に1から9の数字か0だけでできているということ。\/は\が直後の文字をメタ文字(正規表現で使う記号)として扱わないという意味で’/’を文字のまま扱ってパスの区切りスラッシュを全て除くということ。 if (path.includes("items") && path.includes("transactions") && /^([1-9]\d*|0)$/.test(params))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【rails×js】簡単実装できるJavaScript/jQuery小技集

実装すること PFでJavascriptを使用して実装した便利?な機能を紹介します。 実装はとても簡単ですので試してみてください。 ①トップページに戻る機能 ②タイピング風に文字が表示される機能 ③文字数をカウントしてくれる機能 ④入力したpasswordを表示する機能 ⑤データを円グラフで表示してくれる機能(Chart.jsの実装: https://qiita.com/tani__san929/items/cb6640173ff801acc203) 完成形 ①トップページに戻る機能 * 下に進むと右下にボタンが表示されます。 * 押すとトップページに戻ります。 ②タイピング風に文字が表示される機能 * 特定の文字がタイピングされているように順番に表示されます。 * リロードすると再び同じ動きをします。 ③文字数をカウントしてくれる機能 * 特定のフォームに文字が打ち込まれるとカウントされる。 * 上限を設定することで●文字が、赤色に変わるように設定している。 ④入力したpasswordを表示する機能 * devise機能を使用しており、passwordは暗号化(見れないように)されている。 * パスワードを表示するをチェックすることで打ち込んだパスワードが見れるようになります。 ⑤データを円グラフで表示してくれる機能 * Qiita記事で実装方法を書いてます。よかったら。 Char.jsの実装: https://qiita.com/tani__san929/items/cb6640173ff801acc203 実装 ①トップページに戻る機能 ますは、viewにID等を記載します。 どのページでもボタンを表示したいので、layout/application.html.erbファイルに下記を記述します。 <p id="pageTop"><a href="", {"data-turbolinks"= "false"}><i class="fa fa-chevron-up"></i></a></p> pageTopのidをjsファイルで指定することで、その箇所、タグに機能を持たせる事ができます。 layouts/application.html.erb <body> <header> <%= render "layouts/header" %> </header> <main> <%= yield %> </main> <p id="pageTop"><a href="", {"data-turbolinks"= "false"}><i class="fa fa-chevron-up"></i></a></p> <footer> <%= render "layouts/footer" %> </footer> </body> 次は、jsファイルの作成です。 assets/javascripts/application.js $(function(){ var topBtn=$('#pageTop'); topBtn.hide(); //◇ボタンの表示設定 $(window).scroll(function(){ if($(this).scrollTop()>80){ //---- 画面を80pxスクロールしたら、ボタンを表示する topBtn.fadeIn(); }else{ //---- 画面が80pxより上なら、ボタンを表示しない topBtn.fadeOut(); } }); // ◇ボタンをクリックしたら、スクロールして上に戻る topBtn.click(function(){ $('body,html').animate({ scrollTop: 0},500); return false; }); }); 最後にCSSでボタンを装飾します。 assets/stylesheets/application.scss #pageTop { position: fixed; bottom: 20px; right: 20px; } #pageTop i { padding-top: 6px } #pageTop a { display: block; z-index: 999; padding: 15px 10px; border-radius: 30px; width: 60px; height: 60px; background-color: #9FD6D2; color: #fff; font-weight: bold; text-decoration: none; text-align: center; } 完成。 ②タイピング風に文字が表示される機能 まずは、viewへ記述します。 bootstarapやdivなど不必要な箇所はなるべく省略してあります。 タイピング風に表示したい文字(タグ)にclass="typ"を記述します。 私は下記の2行に追加してます。 ページのトップにjquerを使用するための記述も追加します。 views/homes/top.html.erb <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script> ---省略--- <p class="typ">Answer.ly(アンサリー)は、留学に関する質問と回答を通して、</p> <p class="typ">留学に対する不安を無くし、有意義な留学・海外体験を支援しています。</p> 次は、jsファイルの作成です。 assets/javascripts/application.js $('.typ').children().andSelf().contents().each(function() { if (this.nodeType == 3) { $(this).replaceWith($(this).text().replace(/(\S)/g, '<span>$1</span>')); } }); $('.typ').css({'opacity':1}); for (var i = 0; i <= $('.typ').children().size(); i++) { $('.typ').children('span:eq('+i+')').delay(100*i).animate({'opacity':1},50); }; }); 完成。 ③文字数をカウントしてくれる機能 まずは、viewファイルにid=countUp記述。 text_area内ではなく、別途divタグなどので場所を作成してください。 views/contacts/new.html.erb <div class="form-group"> <%= f.label :お問い合わせ詳細 %> <div><span id=countUp>0</span>文字</div> <%= f.text_area :message, rows:8, placeholder:"お問い合わせ内容をご記入ください。(500文字以内)", class:"form-control" %> </div> 次に、jsファイルの作成です。。 assets/javascripts/application.js $(function () { $("textarea").keyup(function(){ var counter = $(this).val().length; $("#countUp").text(counter); if(counter == 0){ $("#countUp").text("0"); } if(counter >= 500){ $("#countUp").css("color","red"); } else { $("#countUp").css("color","#666"); } }); }); 500の部分が自分で設定した文字数の上限です。 それ以下だと、色はグレー色。 500文字に達する(以上だ)と数字は赤色に変わるように設定します。 if(counter >= 500){ 完成。 ④入力したpasswordを表示する機能 まずは、viewの記述です。 * passwarod_fied内にid:"js-password"を記載。 * その下に、チェックボタンを設置。こちらには、id="js-passcheck"を記載。 views/members/registrations/new.html.erb <div class="form-group"> <%= f.label :パスワード %> <% if @minimum_password_length %> <span class="small"></span> <% end %> <%= f.password_field :password, id:"js-password", size:"40%", placeholder:"半角英数記号#{ @minimum_password_length}文字以上", autocomplete: "new-password", class:"form-control password" %> </div> <label for="js-passcheck">パスワードを表示する</label> <input type="checkbox" id="js-passcheck"/></p> 次にjsファイルを作成。 passwordで見えなくなってる部分を☑︎にチェックが入ってるとtextで表示する記述です。 assets/javascripts/application.js $(function() { var password = '#js-password'; var passcheck = '#js-passcheck'; changeInputtype(password, passcheck); }); function changeInputtype(password, passcheck) { $(passcheck).change(function() { if ($(this).prop('checked')) { $(password).attr('type','text'); } else { $(password).attr('type','password'); } }); } 以上です。 最後に 復習も兼ねてまとめてみました。 PFでどう活きてくるかわかりませんが、ユーザビリティを考えた際に思い追加ものです。 また、振り返ってみるとわかってない記述もあったりして知識不足だなと実感してます。 JavaScriptは本格的に勉強していないので、しっかり勉強しないとなーと。 参考書も買ってたので近々そちらで学習予。ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Routerでルーティング(React + TypeScript環境)

この記事では、React Routerというライブラリを用いてルーティングを行う方法について説明しています。 1. React + TypeScript プロジェクトの作成 こちらの記事で詳しく紹介していますが、以下のコマンドでプロジェクトを作成します。 npx create-react-app react-sample --template typescript 2.react-router-dom をインストール TypeScript版のreact-router-domをインストールします。 npm install react-router-dom @types/react-router-dom 3.遷移したいページのコンポーネントを作成 src/以下に遷移したいページのコンポーネントを作成します。 今回はFormというページを作成したいので、以下のようなファイルを作成します。 src/Form.tsx import React from "react"; import "./Form.css"; const Form = () => { return <div className="form"> Form Component</div>; }; export default Form; src/以下にCSSも作成しておきます。 src/Form.css .form { text-align: center; margin-top: 40px; font-size: 28px; } 4.index.tsxを書き換え 先ほど作成したFormコンポーネントと、react-router-domから必要なモジュールを読み込んで利用します。 src/index.tsx import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import Form from "./Form"; import reportWebVitals from "./reportWebVitals"; import { Route, BrowserRouter } from "react-router-dom"; ReactDOM.render( <React.StrictMode> <BrowserRouter> <div> <Route exact path="/" component={App} /> <Route exact path="/form" component={Form} /> </div> </BrowserRouter> </React.StrictMode>, document.getElementById("root") ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); <Route exact path="/" component={App} /> <Route exact path="/form" component={Form} /> 上記ののコード部分でルーティングの設定を行っていて、/に接続するとトップページ、/formに接続すると3.で作成したページが表示されます。 http://localhost:3000/に接続 http://localhost:3000/formに接続 リンクを追加 作成したページへのリンクを作成します。 src/App.tsxにトップページのコードが記載されているので、こちらにリンクを追加します。 import { Link } from "react-router-dom"; <Link className="App-link" to="/Form"> Form </Link> 上記のコードをsrc/App.tsxに追加 src/App.tsx import React from "react"; import logo from "./logo.svg"; import "./App.css"; import { Link } from "react-router-dom"; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.tsx</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> <Link className="App-link" to="/Form"> Form </Link> </header> </div> ); } export default App; ここまで完了して、http://localhost:3000/formに接続にするとFormへのリンクが表示され、クリックするとルーティングが行われます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Javascript】 分割代入について(オブジェクト編)ーDestructuring Objects

初めに ES6に新たに導入された便利な構文の一つに「分割代入(destructuring)」があります。前回に引き続き、分割代入について学習した内容をもとにいろいろ試してみました。外部APIを利用する際に非常に役に立つので記事にしてみました。 前回の記事 :https://qiita.com/redrabbit1104/items/3ba4783c658ebfa624a0 ※内容に間違いなどがある場合はご指摘をよろしくお願いします。 オブジェクトを分割代入してみる ① 配列の代入と違うところ(注意点) 配列を分割代入する場合、配列の中身はindexによって取得できるため変数名を指定するだけで済みました。これに対してオブジェクトはkeyとvalueが存在するので書き方が若干違います。 //配列の場合 const flowers = ['薔薇', '桜', '朝顔', 'ひまわり'] const [a, b, c, d] = flowers; console.log(a, b, c, d); 指定した変数a,b,c,dにそれぞれの値が入っていることが分かります。 同じ要領でfruitというオブジェクトで試してみました。 //オブジェクトの場合 const fruits = { name: 'apple', price: 200, taste: 'good', } const { a, b, c } = fruits; console.log(a, b, c); 結果はundefinedです。オブジェクトはkeyとvalueの2つの要素になっているので、変数名はjavascript側で認識できるような形にしなければなりません。 ②key名を変数名として使う オブジェクトを分割代入したい場合にはkey名を変数名に指定すれば簡単に値が取れます。 //key名を変数名として指定する const fruits = { name: 'apple', price: 200, taste: 'good', } const { name, price, taste } = fruits; console.log(name, price, taste); 変数name, price, tasteにそれぞれの値が代入されていることがわかる。 しかし、このままでは代入する変数名とオブジェクトのkey名が同じになってしまいます。 ③key名:変数名 オブジェクトを分割代入として認識させるにはkey名がなければなりません。そこでkey名の横に':'を付けてその隣に指定したい変数名を入れる方法があります。 //name,price,tasteのキーを変数a,b,cに指定する const fruits = { name: 'apple', price: 200, taste: 'good', } const { name: a, price: b, taste: c } = fruits; console.log(a, b, c); 結果としてキーの値(value)が出力されます。違うのは今回はa,b,cという変数名であり、キー名ではありません。 ④デフォルト値を指定 配列の分割代入と同様、オブジェクトの分割代入も取れた値がなかった場合のためのデフォルト値を指定することができます。デフォルト値は'=値'にすることで指定できます。4番目のplaceはfruitsの中身として存在すると仮定した上で、任意に付けたキー名です。 const fruits = { name: 'apple', price: 200, taste: 'good', } const { name: a = 'null', price: b = 'null', taste: c = 'null', place = 'null' } = fruits; console.log(a, b, c, place); placeというキーは存在しないため、デフォルト値に指定した'null'が表示されます。 ④宣言した変数の値を分割代入で上書き 一度宣言した変数をオブジェクトにある変数に上書きすることもできます。配列の分割代入と同じやり方でやってみます。 const fruits = { name: 'apple', price: 200, taste: 'good', } let name = 'banana'; let price = 300; let taste = 'not bad'; { name, price, } = fruits; console.log(name, price, taste); 見事にエラーが出ます。原因としては{}はjavascriptではcode blockとして認識するため、{で始まるとシンタックスエラーが出ます。これは即時関数の時と同じです。 なのでコードを( )で囲ってあげます。 const fruits = { name: 'apple', price: 200, taste: 'good', } let name = 'banana'; let price = 300; let taste = 'not bad'; ({ name, price, } = fruits); // ( )で囲む  console.log(name, price, taste); nameとpriceが'apple'と200に上書きされました。tasteはそのまま'not bad'で表示されます。 ⑤ネストされているオブジェクトから分割代入して値を取る オブジェクトの中にオブジェクトがある場合には次のように分割代入します。 const fruits = { apple: { place: { tokyo: 'apple', nagoya: 'banana', koube: 'orange' }, }, }; const { place: { tokyo, nagoya, koube }, } = fruits.apple; console.log(tokyo, nagoya, koube); 若干複雑になってきますが、値はちゃんと取れてます。 参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript 変数】var、let、constの違いや使い分けなど

変数 変数とは文字列や数値のデータを格納して、何度も使えるようにする箱のようなものです。 変数を宣言するときには以下のように、右の値を左の変数に代入することになります。 var "変数名" = "値や文字列"; var a = 1; let apple = 3; const banana = 'バナナ'; var、let、constはJavaScriptで変数を宣言すると気に使うキーワードです。 var, let , constの使い分け var, let, constはそれぞれできること、できないことがあります。 var let const 再代入 ○ ○ × 再宣言 ○ × × スコープ 関数 ブロック ブロック 再代入 再代入とは宣言した変数の値を再び別の値で代入することです。 varやletで宣言した変数を再代入とすると代入後の値が適用されます。 constで代入するとするとエラーが出ます。 // var var a = 1 // 変数の値を宣言 a = 2 // 再代入 console.log(a) // '2'と出力される。再代入された'a = 2'が適用されるため。 // let let b = 1 // 変数の値を宣言 b = 3 // 再代入 onsole.log(b) //'3'と出力される。再代入された'b = 3'が適用されるため。 // constはエラーがでる。 再宣言 再宣言とは一度宣言した変数を再び別の値で宣言しなおすことです。 varで再宣言すると、あとに宣言した変数が適用されます。 letやconstやで再宣言するとエラーがでます。 //var var a = 1 //最初の宣言 var a = 2 //2回目の宣言 console.log(a) //'2'と出力される。2回目で再宣言された a = 2 が適用されるため。 //let, constはエラーがでる。 スコープ スコープとは宣言した変数が使える範囲のことです。 varは関数スコープ、letやconstやはブロックスコープの範囲で変数の値が適用されます。 関数スコープ 関数とは複数の処理をひとまとめにしたものです。 varで変数を宣言すると、同じ関数内であればどこでも呼び出せることができます。 //関数 function kansuu { //ifで条件づけられたブロック if (true) { var a = 1 } console.log(a) // '1'が出力される。varで変数を宣言すると関数内のどこでも呼ぼび出すことができるため。 } ブロックスコープ ' { } 'でくくられた範囲のことをブロックといいます。 letやconstで変数を宣言すると、同じ関数内であってもブロック内でなければ変数を呼び出すことができません。 //関数 function kansuu { //ifで条件づけられたブロック if (true) { let a = 1 const b = 2 console.log(a) // '1'が出力される。ブロック内で'a'の値を呼び出したため。 } console.log(b) // 出力されない。ブロック外で'b'の値を呼び出したため。 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのminify(最小化・圧縮)の解説をいくつかまとめてみた

JavaScriptを手で書いたあと、Webで公開する際にはminifyすることが定番、のはず。 けれども、minifyしたあとのJavaScriptは元と全く同じ動作をするが、文法すら全く異なるように最小化されることが多い。 JavaScript初心者のわたしとしては?????となることが多かったので、言語仕様を勉強するためにもいくつかまとめてみた。 例1:ローカル変数の置き換え minify前 (function (position) { const SOURCE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; return SOURCE[position]; })(3); minify後 (function(a){const b="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";return b[a]})(3); 解説 JavaScriptのスコープには、「グローバルスコープ」と「ローカルスコープ(関数スコープとブロックスコープ)」があり、グローバルスコープを汚さないために即時関数を利用することがある。 当然、即時関数内の変数名は自由に設定できるので、aとかbとか1文字で適当に置き換える。 例2:trueとfalseの置き換え minify前 let a = true; let b = false; if(c===true){ //(略) minify後 let a=!0,b=!1;if(!0===c){//(略) 解説 a = trueとするよりも!0(0の反対、論理否定演算子)としたほうが短い。trueは4文字だけど!0は2文字で済む。 なお、JavaScriptにおいて、trueと!falseと!0は完全に厳密に同値です。同様に、falseと!trueと!1も。 (なぜこうなるか理由がわからなかったので詳しい方コメント下さい…) console.log(true===!false);//true console.log(!false===!0);//true console.log(!0===true);//true console.log(false===!true);//true console.log(!true===!1);//true console.log(!1===false);//true ちなみにJavaScriptでfalseと評価されるのはfalse、''(空文字)、NaN、0、-0、undefined、nullの7つらしい ちなみに ↑のスクリプトでminifyされたあと、!0===cとヨーダ記法(==などの比較演算子の左側にリテラルを書くこと)になっているが理由は謎。 例3:論理演算子&&と||の活用 minify前 var root; if (typeof self == 'object' && self.self === self) { root = self; } else if (typeof global == 'object' && global.global === global) { root = global; } else { root = this; } ちょっとminify後 var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this; 参考より引っ張ってきました。 JavaScriptの仕様として、引用元 OR "||" 演算子は次のように動きます: 1.左から右にオペランドを評価します。 2.それぞれのオペランドで、それを Boolean に変換します。もしも結果が true であれば、停止しオペランドの本来の値を返します。 3.もしもすべての他のオペランドが評価された場合(i.e. すべて 偽 のとき), 最後のオペランドを返します。 AND "&&" 演算子は次のように動きます: 1.左から右にオペランドを評価します。 2.それぞれのオペランドで、それを Boolean に変換します。もしも結果が false の場合、ストップしそのオペランドの本来の値を返します。 3.もしもすべての他のオペランドが評価された場合(i.e. すべて 真 のとき), 最後のオペランドを返します。 というのがあります。 個人的にこう理解した OR 要するに、||は左から順にみていって最初にtrueになるものを返すということです。 ◯ || △ || □のようなとき、||は1つでもtrueになれば全体としてtrueになりますね。 なので、左から順にみていって、1つでもtrueなものが見つかればそいつを返します。 AND 一方、&&は左から順にみていって最初にfalseになるものを返す、ということです。 ◯ && △ && □のようなとき、&&は1つでもfalseになれば全体としてfalseになりますね。 なので、左から順にみていって、1つでもfalseなものが見つかればそいつを返します。 ANDとORの優先順位 また、&&演算子の方が||より優先順位が高いです。 これを思いっきり利用したのが↑のスクリプトです。 例 &&の方が優先順位が高いので、例えば 'ぴえん?' && 'ぱおん?' || 0 && 1 && 2 は"ぱおん?"が返ります。 まず'ぴえん?' && 'ぱおん?'を評価して"ぱおん?"が返り、次に0 && 1 && 2を評価して0が返り、最後に"ぱおん?" || 0を評価して"ぱおん?"が返るイメージです。 例4:カンマ演算子 minify前 let a=function(event){ event.preventDefault(); event.hogehoge(); return event.result; } minify後 let a=function(e){return e.preventDefault(),e.hogehoge(),e.result}; いい例が見つからんかった… JavaScriptの,は演算子で、それぞれの中身を左から右に順番に評価し、最後の式の値を返します。 minifyされたJSファイルのいたるところで,は使われますが、結局のところ左から右に順番に評価し、最後の式の値を返すを理解していれば解読できます。 最後に magnify(大きくする)という英単語があるんだから、minifyは当然「小さくする」って意味だろう と思って非プログラマーの英語の先生にminifyと言ってみたら、「何いってんだお前」と言われました。 minifyは英語の辞書には載ってません。プログラマーにのみ通じる単語です? 大きくするって単語はmagnifyとかenlargeって単語があるのに、小さくするのはmake ◯◯ smallerしかないのなんで? 最後に2 わたしJavaScript初心者なので間違いあったらごめんなさい?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsとReact.jsのプロジェクト作成から公開準備(※コーディングは含みません。)

Vue.jsとReact.jsのプロジェクト作成から公開準備 Javascriptで使用できるフロントエンド側のフレームワーク、VueとReactのプロジェクト作成コマンドを忘れないためと、ビルドしてからの流れについての説明が見当たらず、せっかく個人開発でやってみても誰にもリリースすることがないように記す。 なお、今回はそれぞれポートフォリオをフロントエンド側でのみ、作成したという程で作成しました。 動作環境 MacBook Air (Retina, 13-inch, 2018) BicSur 11.3.1 1.6 GHz Dual-Core Intel Core i5 6 GB 2133 MHz LPDDR3 Node.js v13.8.0 @vue/cli 4.5.13 各プロジェクトの作成 Vue.js vue create vue-portfolio React.js npx create-react-app react-portfolio インストール確認 Vue.js ..省略.. ? Successfully created project vue-portfolio. ? Get started with the following commands: $ cd vue-portfolio $ npm run serve React.js ..省略.. Success! Created react-portfolio at XXXXXXX/react-portfolio Inside that directory, you can run several commands: npm start Starts the development server. npm run build Bundles the app into static files for production. npm test Starts the test runner. npm run eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd react-portfolio npm start Happy hacking! インストール後のnpmサーバーを起動して画面の確認 Vue.js ■実行 cd vue-portfolio npm run serve ■結果 DONE Compiled successfully in 5762ms App running at: - Local: http://localhost:8080/ - Network: http://192.168.10.9:8080/ Note that the development build is not optimized. To create a production build, run npm run build. No issues found. ■画像 React.js ■実行 cd react-portfolio npm start ■結果 Compiled successfully! You can now view react-portfolio in the browser. Local: http://localhost:3000 On Your Network: http://192.168.10.9:3000 Note that the development build is not optimized. To create a production build, use npm run build. ■画像 いじる ※自分だけのポートフォリオを作成してください。  今回はわかりやすくトップページのみ日本語に変更をしていきます。 Vue.js .src/views/Home.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png"> <!-- <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/> --> <HelloWorld msg="ようこそ。あなたのVue.jsとTypeScript App"/> //上からの変更点 </div> </template> <script lang="ts"> import { defineComponent } from 'vue' import HelloWorld from '@/components/HelloWorld.vue' // @ is an alias to /src export default defineComponent({ name: 'Home', components: { HelloWorld } }) </script> React.js .src/App.js import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> {/* Edit <code>src/App.js</code> and save to reload. */} <code>src/App.js</code>を編集と保存して再読み込み。{/* 上の行を変更 */} </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App; ポートフォリオ公開準備 自分だけのポートフォリオを作成したら公開するために、ビルドを行なっていきます。 Vue.js STEP1:作成したコードをビルド 以下のコマンドを実行する npm run build STEP2:ビルド結果の確認 ビルド結果 「.dist」配下にビルドしたファイルが作成 DONE Compiled successfully in 11164ms File Size Gzipped dist/js/chunk-vendors.506821c6.js 118.83 KiB 42.80 KiB dist/js/app.67acf451.js 6.65 KiB 2.39 KiB dist/js/about.2b8983e6.js 0.34 KiB 0.26 KiB dist/css/app.aaf04fac.css 0.42 KiB 0.26 KiB Images and other types of assets omitted. DONE Build complete. The dist directory is ready to be deployed. INFO Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html React.js STEP1:作成したコードをビルド 以下のコマンドを実行する npm run build STEP2:ビルド結果の確認 ビルド結果 「.build」配下にビルドしたファイルが作成 Compiled successfully. File sizes after gzip: 41.34 KB build/static/js/2.a9157932.chunk.js 1.63 KB build/static/js/3.4db56475.chunk.js 1.17 KB build/static/js/runtime-main.e0b02365.js 631 B build/static/js/main.5d1e222e.chunk.js 574 B build/static/css/main.9d5b29c0.chunk.css The project was built assuming it is hosted at /. You can control this with the homepage field in your package.json. The build folder is ready to be deployed. You may serve it with a static server: npm install -g serve serve -s build Find out more about deployment here: https://cra.link/deployment 作成したものをAWS S3へアップロードしてリリース ※AWSアカウントを持っている前提で記載しております。 共通 公式HPに公開方法が記載されております。 そのため掻い摘んで説明 STEP1:S3バケットの作成 サービス検索ボックスより「S3」と検索 右側オレンジボタンの「バケットを作成」をクリック STEP2:一般的な設定 バケット名は小文字英数字と半角記号のみで設定(※一意になるように命名) AWSリージョンについては今回はデフォルトのまま(アジアパシフィック(東京)ap-northeast-1) STEP3:このバケットのブロックパブリックアクセス設定 「パブリックアクセスをすべてブロック」のチェックを外す 「パブリックアクセスのブロックをすべてオフにすると、このバケットとバケット内のオブジェクトが公開される可能性があります。」の確認にチェックを入れる STEP4:バケットの作成 指定項目以外についてはデフォルトのまま 右下の「バケットを作成」をクリック STEP5:静的ウェブサイトホスティング設定 作成したバケットをクリック プロパティタブを選択 「静的ウェブサイトホスティング」の「編集」をクリック 「有効にする」にチェックを入れ「ホスティングタイプ」に「静的ウェブサイトをホストする」がチェック入っていること 「インデックスドキュメント」に「index.html」を入力 右下の「変更の保存」をクリック STEP6:バケットポリシー アクセス許可タブをクリック バケットポリシー欄の編集をクリック 以下の内容を入力し、["arn:aws:s3:::Bucket-Name/*"]部分の[Bucket-Name]をバケット名へ変更 右下の「変更の保存」をクリック { "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::XXXXXXXXX/*" ] } ] } STEP7:ビルドしたファイルのアップロード オブジェクトタブから「アップロード」をクリック 以下のファイルに対して、アップロードを行う Vueの場合は「.dist」配下のファイルすべて Reactの場合は「.build」配下のファイルすべて STEP8:外部ネットワークで確認 プロパティタブの「静的ウェブサイトホスティング」にある「バケットウェブサイトエンドポイント」をクリック アップロードした内容が閲覧できること 以上。 Appendix ?ビルドしたプログラムをすぐ見れない? VueもReactも共通してだが、ビルドされた「index.html」についてはサブディレクトリのままでは閲覧することができない。理由はビルド時にファイル内に記載される読み込みファイルが絶対パスのドメイン直下(ルートフォルダ)で記載がされているため、初心者はAWS S3などにあげて確認するほうがよい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Enterキーで次項目へフォーカス移動(jQuery版)

テストページ 目的 似たようなスクリプトは探せば結構あるのですが、問題が多いので1から作りました。 button上でenter押下時も移動するようにしていますが、実際のアプリに組み込んで動作確認したら動作を変えるかもしれません。 enterキー押下時に次項目へ移動するjavascript(jQuery利用) フォーカス移動概要 tabindex順に移動します(tabindex1以上⇒tabindex未指定の順) tabindexがマイナスの項目へは移動しません tabindexがマイナスの項目から移動する場合は、直近前後の項目へ移動します。 通常移動しない項目やにtabindexを付けると移動可能になることを考慮 上記に関連して、フォーカス移動可能な項目が入れ子になっていても移動できる ソース <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script> <script> $(() => { // :focusable はマイナスのtabindexを含む // ⇒enter時に次項目へ移動するためのイベント対象のため含めている。 const elements = ':focusable:not(a)'; $(elements).keypress((e) => { if (e.key === 'Enter') { // submitしない e.preventDefault(); // focus可能な項目が入れ子になっている場合、targetのみで処理する e.stopPropagation(); // tabindex順に移動するためソート let sortedList = $(elements).sort((a,b) => { if(a.tabIndex && b.tabIndex) { return a.tabIndex - b.tabIndex; } else if(a.tabIndex && !b.tabIndex) { return -1; } else if(!a.tabIndex && b.tabIndex) { return 1; } return 0; }); if (e.target.tabIndex < 0) { // tabindexがマイナスの場合、DOM上で次の項目へ移動するためソート前の項目から検索する sortedList = elements; } // 現在の項目位置から、移動先を取得する const index = $(sortedList).index(e.target); const nextFilter = e.shiftKey ? `:lt(${index}):last` : `:gt(${index}):first`;  const nextTarget = $(sortedList).filter(nextFilter); // shift + enterでtagindexがマイナスの項目へ移動するのを防ぐ if (!nextTarget.length || nextTarget[0].tabIndex < 0) return; // フォーカス移動+文字列選択 nextTarget.focus(); if (typeof nextTarget.select === 'function') nextTarget.select(); } }); }); </script> フォーカス移動の仕様(手で動かしながら調べたので違っているかも) input, button, select, textarea, aがフォーカス移動可能なelement 上記以外でも、tabindex属性を与えると移動対象となる disabled、見えない項目は移動対象外 tabindex(1以上)の順に移動 ⇒ 最大まで行ったらtabindex未指定(or 0,空白)をDOM出現順に移動 ⇒ URL入力欄に移動 同じtabindexが複数ある場合はDOM出現順に移動 tabindex=0やtabindex=""は未指定と同じ扱い tabindexがマイナスの項目には移動しない。 tabindexがマイナスの項目にフォーカスがある場合、次項目はDOM出現順に移動可能な項目へ移動 Shiftを押下時は逆順に移動 ラジオボタンは同じnameを持つ場合、グループ化される。 ・・・【制御がややこしいため、未サポート】 グループがチェックを持つ場合は、そこへフォーカスする。 チェックがない場合は、グループの先頭にフォーカスする。 同一グループ内で異なるtabindexを持つ場合 チェックがなければ、それぞれのtabindex毎にグループ化されるような動き チェックがあれば、チェックがある箇所のみが移動対象となる その他 anchorも移動対象 移動しても、Enterでリンク先に移動してしまうので対象から外しています。 制限事項 タブ移動時、ラジオボタンは同一nameでグループ化されるのがTabでの動作ですが、そこまで細かい制御はできていません。 参考資料 https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation jqueryを使った場合の移動について セレクタで「:focusable」「:tabbable」があるが、tabindexの順番まで考慮して並べ替えはしてくれない模様。 ⇒ $().sort()で並び替えればよい。 api-focusable-selector api-tabbable-selector
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwitchBot温湿度計を Web Bluetooth API でスキャンする【試行錯誤中】

表題の通りです。 Node.js や Python等を使って SwitchBot温湿度計からの情報取得を実現した記事をいくつか見かけていました。 そして、Web Bluetooth API でやってみようと思って手をつけずにいたのですが、今回試してみて試行錯誤の過程を含めて自分用メモとして記事にしました。 Web Bluetooth API でスキャンしてみる スキャンに必要な情報 公式から以下の情報が提供されていました。 ●Meter BLE open API · OpenWonderLabs/python-host Wiki  https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API 上記の仕様を見ると UUID は cba20d00-224d-11e6-9fb8-0002a5d5c51b になるようです。 そこで、まずはブラウザの開発者ツールのコンソールで、以下の UUID をフィルタに使った形の処理で実行してみます。 navigator.bluetooth.requestDevice({filters: [{ services: ["cba20d00-224d-11e6-9fb8-0002a5d5c51b"] }]}); そうすると、以下のとおりスキャン結果に「WoSensorTH」という名前が表示されました。 冒頭に書いた仕様を見ると SwitchBot温湿度計で間違いないようです。 別途、温湿度情報の取得はやってみようと思います。 スキャン時のフィルターとして利用できるもの 上記のとおり、スキャン時のフィルタでは UUID を利用しました。 フィルタの指定で他にも利用できるものがあるので、ここで補足しておきます。 利用できるものは、他には例えば「name」や「namePrefix」があります。それらを今回の例で用いると、以下のような指定も行えます。 navigator.bluetooth.requestDevice({filters: [{ name: ["WoSensorTH"] }]}); navigator.bluetooth.requestDevice({filters: [{ namePrefix: ["WoSensor"] }]}); 情報の参照元は以下です。 ●Bluetooth.requestDevice() - Web APIs | MDN  https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/requestDevice ●Web Bluetooth  https://webbluetoothcg.github.io/web-bluetooth/ 【追記】 【追記1】 上記と違うやり方になるかも... この続きの記事を書こうと追加調査や試行錯誤の続きをやっていたのですが、これまで toio・M5Stack・micro:bit とのやりとりに使っていた処理とは異なるものを使う必要があるかも... 試行錯誤をしていた時の情報(自分用のメモ) 以下は、上記の手順にたどり着くまでに調べたり試したりしていたことなので、ご興味がある方はご覧ください。 試行錯誤の過程で試した内容を残すため、自分用に書いているものです。 Apple の Bluetooth Explorer でのスキャンを試す Web Bluetooth API でスキャン等を行っていく前に、まずは Apple が提供している Bluetooth Explorer でスキャンできるかを確認しました。Bluetooth Explorer は以下の記事を書いた時などにも使ったことがあるものです。 ●#UIFlow の BLE UART を使った文字のやりとりを #M5Stack_Core2 で試してみた( #M5Stack ) - Qiita  https://qiita.com/youtoy/items/0aeac01927d60c33f421 公式アプリで MACアドレスを確認 Bluetooth Explorer でのスキャンを行った結果を見て SwitchBot温湿度計が含まれるかを判別できる情報として、以下の記事でも利用している MACアドレスが使えそうでした。その情報を確認する方法を公式アプリを操作してみて試してみます。 ●SwitchBot 温湿度計の測定値を BLE Advertisement パケットから直接読み取る - Qiita  https://qiita.com/warpzone/items/11ec9bef21f5b965bce3 なお、この記事で使っている SwitchBot温湿度計は、既に公式アプリで情報を取得できるようセットアップを済ませている状態です。 公式アプリで MACアドレスを表示させる iPhone の公式アプリを開き、SwitchBot温湿度計が一覧に出てくるか確認します。以下のとおり、一覧の中に温湿度の情報とともに表示されました。 上記の温湿度計の部分をアプリ上でタップします。そうすると、以下の履歴データの表示画面に遷移しました。 ここで、画面右上に表示されている歯車アイコンをタップします。 そうすると、以下の画面が表示されました。さらに、画面右上の 3つの点のアイコンの部分をタップします。 無事に以下のように MACアドレスが表示されました。 Bluetooth Explorer でスキャンしてみる MACアドレスも判明したので、Bluetooth Explorer でスキャンしてみます。 スキャン方法は、上で紹介した過去の記事の「Mac からのスキャン」という項目の中で説明しています。 その結果、上記の MACアドレスがスキャン結果に出てきました。 ブラウザから Web Bluetooth API でスキャン 開発者ツールのコンソールからスキャンする まずは、過去に書いた以下の記事で使った方法で試してみます。 ●【JavaScript 2020】 #UIFlow の BLE UART を使ったブラウザから #M5Stack_Core2 ( #M5Stack )への文字の送信 - Qiita  https://qiita.com/youtoy/items/3da58570972803134f6c 具体的には、ブラウザで開発者ツールのコンソールを開き、そこで以下の内容を実行してみるというものです。 navigator.bluetooth.requestDevice({ acceptAllDevices: true }) スキャンをしてみたものの、それらしい情報が見当たらないかも? (※ この時は見落としていただけの可能性あり) 情報を探してみる お試しをしていてうまくいってない感じがしたので、追加で情報を検索したりして、最終的に冒頭の手順にたどり着きました。 ●ronschaeffer/sbm2mqtt: Grab SwitchBot Meter data from Bluetooth Low Energy advertisements and publish them to an MQTT topic for use with Home Assistant, etc.  https://github.com/ronschaeffer/sbm2mqtt ●SwitchBot温湿度計の値をRaspberryPiでロギング - Qiita  https://qiita.com/c60evaporator/items/7c3156a6bbb7c6c59052 ●SwitchBot温湿度計をGrafanaで可視化 - kamijin-fanta  https://blog.kamijin-fanta.info/2021/03/temperature-collector/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwitchBot温湿度計を Web Bluetooth API でスキャンする

表題の通りです。 Node.js や Python等を使って SwitchBot温湿度計からの情報取得を実現した記事をいくつか見かけていました。 そして、Web Bluetooth API でやってみようと思って手をつけずにいたのですが、今回試してみて試行錯誤の過程を含めて自分用メモとして記事にしました。 Web Bluetooth API でスキャンしてみる スキャンに必要な情報 公式から以下の情報が提供されていました。 ●Meter BLE open API · OpenWonderLabs/python-host Wiki  https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API 上記の仕様を見ると UUID は cba20d00-224d-11e6-9fb8-0002a5d5c51b になるようです。 そこで、まずはブラウザの開発者ツールのコンソールで、以下の UUID をフィルタに使った形の処理で実行してみます。 navigator.bluetooth.requestDevice({filters: [{ services: ["cba20d00-224d-11e6-9fb8-0002a5d5c51b"] }]}); そうすると、以下のとおりスキャン結果に「WoSensorTH」という名前が表示されました。 冒頭に書いた仕様を見ると SwitchBot温湿度計で間違いないようです。 別途、温湿度情報の取得はやってみようと思います。 スキャン時のフィルターとして利用できるもの 上記のとおり、スキャン時のフィルタでは UUID を利用しました。 フィルタの指定で他にも利用できるものがあるので、ここで補足しておきます。 利用できるものは、他には例えば「name」や「namePrefix」があります。それらを今回の例で用いると、以下のような指定も行えます。 navigator.bluetooth.requestDevice({filters: [{ name: ["WoSensorTH"] }]}); navigator.bluetooth.requestDevice({filters: [{ namePrefix: ["WoSensor"] }]}); 情報の参照元は以下です。 ●Bluetooth.requestDevice() - Web APIs | MDN  https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/requestDevice ●Web Bluetooth  https://webbluetoothcg.github.io/web-bluetooth/ 試行錯誤をしていた時の情報(自分用のメモ) 以下は、上記の手順にたどり着くまでに調べたり試したりしていたことなので、ご興味がある方はご覧ください。 試行錯誤の過程で試した内容を残すため、自分用に書いているものです。 Apple の Bluetooth Explorer でのスキャンを試す Web Bluetooth API でスキャン等を行っていく前に、まずは Apple が提供している Bluetooth Explorer でスキャンできるかを確認しました。Bluetooth Explorer は以下の記事を書いた時などにも使ったことがあるものです。 ●#UIFlow の BLE UART を使った文字のやりとりを #M5Stack_Core2 で試してみた( #M5Stack ) - Qiita  https://qiita.com/youtoy/items/0aeac01927d60c33f421 公式アプリで MACアドレスを確認 Bluetooth Explorer でのスキャンを行った結果を見て SwitchBot温湿度計が含まれるかを判別できる情報として、以下の記事でも利用している MACアドレスが使えそうでした。その情報を確認する方法を公式アプリを操作してみて試してみます。 ●SwitchBot 温湿度計の測定値を BLE Advertisement パケットから直接読み取る - Qiita  https://qiita.com/warpzone/items/11ec9bef21f5b965bce3 なお、この記事で使っている SwitchBot温湿度計は、既に公式アプリで情報を取得できるようセットアップを済ませている状態です。 公式アプリで MACアドレスを表示させる iPhone の公式アプリを開き、SwitchBot温湿度計が一覧に出てくるか確認します。以下のとおり、一覧の中に温湿度の情報とともに表示されました。 上記の温湿度計の部分をアプリ上でタップします。そうすると、以下の履歴データの表示画面に遷移しました。 ここで、画面右上に表示されている歯車アイコンをタップします。 そうすると、以下の画面が表示されました。さらに、画面右上の 3つの点のアイコンの部分をタップします。 無事に以下のように MACアドレスが表示されました。 Bluetooth Explorer でスキャンしてみる MACアドレスも判明したので、Bluetooth Explorer でスキャンしてみます。 スキャン方法は、上で紹介した過去の記事の「Mac からのスキャン」という項目の中で説明しています。 その結果、上記の MACアドレスがスキャン結果に出てきました。 ブラウザから Web Bluetooth API でスキャン 開発者ツールのコンソールからスキャンする まずは、過去に書いた以下の記事で使った方法で試してみます。 ●【JavaScript 2020】 #UIFlow の BLE UART を使ったブラウザから #M5Stack_Core2 ( #M5Stack )への文字の送信 - Qiita  https://qiita.com/youtoy/items/3da58570972803134f6c 具体的には、ブラウザで開発者ツールのコンソールを開き、そこで以下の内容を実行してみるというものです。 navigator.bluetooth.requestDevice({ acceptAllDevices: true }) スキャンをしてみたものの、それらしい情報が見当たらないかも? (※ この時は見落としていただけの可能性あり) 情報を探してみる お試しをしていてうまくいってない感じがしたので、追加で情報を検索したりして、最終的に冒頭の手順にたどり着きました。 ●ronschaeffer/sbm2mqtt: Grab SwitchBot Meter data from Bluetooth Low Energy advertisements and publish them to an MQTT topic for use with Home Assistant, etc.  https://github.com/ronschaeffer/sbm2mqtt ●SwitchBot温湿度計の値をRaspberryPiでロギング - Qiita  https://qiita.com/c60evaporator/items/7c3156a6bbb7c6c59052 ●SwitchBot温湿度計をGrafanaで可視化 - kamijin-fanta  https://blog.kamijin-fanta.info/2021/03/temperature-collector/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【駆け出しエンジニア支援】HTML&CSS + jQueryだけで簡単にコンポーネントを実装してみる【初心者向け】

概要 最近はVueやReactといった便利なライブラリを使い、フロントのHTMLやJSファイルを部品として分割するコンポーネント化の技術の発展がめざましいですね。しかしそれらの技術は便利な反面、環境への依存度が高かったり、学習コストが高いのも課題の一つです。 この記事では技術の発展にあえて逆行し、HTML&CSSおよびjQueryだけで簡単にビューの部品をコンポーネント化する方法を提案します。LP製作やコーポレートサイト制作程度のフロントエンド業務であれば、この記事で解説する簡単な実装でも十分実務で活用できる場合もあるかと思うので、こんな簡単な方法もあるのだなと、記憶の片隅にでも入れてもらえればと思います。 この記事の想定読者 Web系エンジニア初心者 HTML&CSS, jQueryは理解している Vue.jsやReact.jsが分からない/ 学習中だが躓いている それでもDRY(Don't Repeat Yourself)したい この記事の想定読了時間 20分程度 完成図 実装コード index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Kiita</title> <script src="https://code.jquery.com/jquery-3.6.0.slim.min.js"></script> <link href="https://fonts.googleapis.com/css2?family=Goldman&amp;display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.9.0/css/all.css"> </head> <body> <myHeader></myHeader> <myBody></myBody> <!-- コンポーネント --> <script src="components/js/myHeader.js"></script> <script src="components/js/myBody.js"></script> <script src="components/js/sidebarContent.js"></script> <script src="components/js/topContentArticle.js"></script> <style> * { margin: 0; } a { text-decoration: none; color: inherit; } body { background-color: #F0F0F0; } </style> </body> </html> ほか8ファイル(js, css) 解説 まず大前提として、HTMLでは<div></div>といったタグを用いることができますが、これらのタグは自作(自前で定義)することもできます。さらに、そのタグをjQueryから取得することが可能です。 例えば以下に示すindex.htmlのbodyタグ内には<myHeader></myHeader>というタグがあります。このタグはjQuery上から$('myHeader')として取得することが可能です。 index.html <body> <myHeader></myHeader> ←これ <myBody></myBody> <!-- コンポーネント --> <script src="components/js/myHeader.js"></script> <script src="components/js/myBody.js"></script> <script src="components/js/sidebarContent.js"></script> <script src="components/js/topContentArticle.js"></script> <style> * { margin: 0; } a { text-decoration: none; color: inherit; } body { background-color: #F0F0F0; } </style> </body> jQuery上から要素にアクセスできるということは、 $('myHeader').html('hoge') と書くとHTMLを <myHeader>hoge</myHeader> という状態にできるワケです。つまりhogeの部分がヘッダーの中身になっていればいいワケですから、 <script src="components/js/myHeader.js"></script> っていうスクリプトタグを書いてから、その中身に components/js/myHeader.js $('myHeader').html(` <header> <div id="header"> <p id="header-logo">Kiita</p> </div> </header> <link rel="stylesheet" href="components/css/myHeader.css"> `) って書けばヘッダーがコンポーネント化できるワケです。ここにCSSをあてたければ、単にlinkタグを追加すればいいだけなので、 components/js/myHeader.js $('myHeader').html(` <header> <div id="header"> <p id="header-logo">Kiita</p> </div> </header> <link rel="stylesheet" href="components/css/myHeader.css"> `) ってすればCSSが適用できます。あとは<script src="components/js/myHeader.js"></script>を各ページに追加すればヘッダーが共通部品になる、即ちコンポーネント化されるので、DRYが達成できています。やったZE! しかし、コンポーネント化の真髄はここからです。例えば画面左側のサイドバーの『ホーム』『タイムライン』などが書いてある部分に注目してください。 この部分もコンポーネントが使用されています。<sidebarContent></sidebarContent>です。 myBody.js <div id="top-sidebar-article"> <p class="sidebar-title">記事フィード</p> <sidebarContent href="" icon="fa fa-home" title="ホーム"></sidebarContent> <sidebarContent href="" icon="far fa-clock" title="タイムライン"></sidebarContent> <sidebarContent href="" icon="fa fa-chart-line" title="トレンド"></sidebarContent> </div> 一般的なHTMLタグのようにhrefなどの属性を与えることも可能です。このように与えられた属性は$('sidebarContent').attr('href')などとして取得できます。 よって、コンポーネントにパラメータを与えて動的に表示内容を変更したい場合は、以下のような処理になります。 sidebarContent.js $('sidebarContent').html(` <a href="${$('sidebarContent').attr('href')}" class="sidebar-content"> <i class="${$('sidebarContent').attr('icon')}"></i> <span>${$('sidebarContent').attr('title')}</span> </a> `) これでパラメータを与えることができますが、まだ一つ問題があります。このコードのままこの処理を走らせると、sidebarContentタグが複数ある場合に全てのコンポーネントが最初に与えたパラメータの値に置き換わってしまいます。 なぜこうなるかというと、$('sidebarContent')が何回目に呼び出されたパラメータを参照すればいいか記述できていないためです。こうした場合は$('sidebarContent')の処理にfilter関数を適用してあげることで解決できます。 sidebarContent.js $('sidebarContent').filter((i, dom) => { $(dom).html(` <a href="${$(dom).attr('href')}" class="sidebar-content"> <i class="${$(dom).attr('icon')}"></i> <span>${$(dom).attr('title')}</span> </a> `) }) これでパラメータを無事個別に受け取ることが可能になります!これでコンポーネント化がバッチリ完了です。しかしまだ細かい修正を行うべき部分が存在するので、あと2箇所ほど修正を加えます。 まず$('タグ名')のように指定している部分ですが、この'タグ名'の部分はファイル名を取得してあげれば毎回書き直す必要がなくなるので、以下のようにファイル名を取得して適用する処理を加えます。CSSファイルも同じ名前なので、こちらもファイル名をタグ名にリンクさせます。 sidebarContent.js tagName = document.currentScript.src.split('/').pop(0).split('.')[0] $(tagName).parent().append(`<link rel="stylesheet" href="components/css/${tagName}.css">`) $(tagName).filter((i, dom) => { $(dom).html(` <a href="${$(dom).attr('href')}" class="sidebar-content"> <i class="${$(dom).attr('icon')}"></i> <span>${$(dom).attr('title')}</span> </a> `) }) また、<sidebarContent></sidebarContent>のような自分で命名したタグはコンポーネントの呼び出し後には削除しても良いので、これを削除する処理を追加します。削除の処理は以下のような記述で可能です。 $(dom).children().unwrap() よってこれらの修正が全て完了すると、以下のようなJavaScriptテンプレートを使用してコンポーネントの追加が可能になります。 リスナテンプレート tagName = document.currentScript.src.split('/').pop(0).split('.')[0] $(tagName).parent().append(`<link rel="stylesheet" href="components/css/${tagName}.css">`) $(tagName).filter((i, dom) => { $(dom).html(`任意のHTML`).children().unwrap() }) このスクリプトが自作HTMLタグの追加リスナとして作用します。 以上でコンポーネント化の解説が完了です!まとめると、以下の手順によってHTML&CSS + jQueryのみでビューのコンポーネント化が可能になります。 1. 自作タグを任意のHTMLファイルに記述する。 index.html <myComponent param="hoge"></myComponent> 2. JSファイルとCSSファイルをタグ名で用意する。 root | -- index.html | -- components | ` -- js | ` -- myComponent.js ` -- -- css ` -- myComponent.css 3. myComponent.jsにリスナテンプレートをペーストして任意のHTMLの部分を編集する。 myComponent.js tagName = document.currentScript.src.split('/').pop(0).split('.')[0] $(tagName).parent().append(`<link rel="stylesheet" href="components/css/${tagName}.css">`) $(tagName).filter((i, dom) => { $(dom).html(` 任意のHTML `).children().unwrap() }) パラメータを自作タグに与えた場合、$(dom).attr('パラメータ名')で取得しましょう。 <myComponent param="hoge" href="fuga"></myComponent> ↕︎ 対応 $(dom).html(` <div>${$(dom).attr('param')}</div> <a href="${$(dom).attr('href')}">リンク</a> `) ↓のようにレンダリング(出力)される <div>hoge</div> <a href="fuga">リンク</a> 4. HTMLファイルからmyComponent.jsを読み込む。 index.html <myComponent param="hoge"></myComponent> <script src="components/js/myComponent.js"></script> 完了! これでHTML&CSS + jQueryのみでコンポーネント化が達成できました。お疲れさまでした。 まとめ jQueryでも割と簡単にコンポーネント化してDRYできる! ReactやVueを一から学ぶのは結構大変なので、簡単なLPやコーポレートサイトのコンポーネント化であればこのような方法もアリなのかなと思います。もちろんVueやReactを習得できるのであれば、それに越したことはありませんが、この記事で紹介したような方法もあるということは覚えてみても損はないかもしれません。 『コンポーネント化』と単語だけ聞くとどうも難しく感じるかもしれませんが、ReactやVueで用いられるコンポーネントの概念もこの記事で説明していることと基本的な考え方は一緒です。一旦理解できてしまえばコードが素早く綺麗にかける素晴らしい技術なので、是非習得して快適なコーディングライフを過ごしましょう! 筆者プロフィール 26歳現役フルスタックエンジニア。Ruby/PHP/JSVue/AWS/nginxなどが主です。プログラミングの教材執筆やメンターしてます。VTuberとしてプログラミング解説雑談配信等もします。 Twitter : https://twitter.com/soeno_onseo Github : https://github.com/Fumiya-Soeno HP : http://www.onseo.info/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む