20210426のJavaScriptに関する記事は26件です。

【JavaScript】ホイスティングについて ~復習~

ReactやTypeScriptを学んでる上でJavaScriptの重要性について感じたので復習していきたいと思います! ホイスティングについて よく記事で『宣言の巻き上げ』というワードを目にするんですがよくわからない。。。 コードを見ながらの方がイメージが湧きやすいと思うので実際にコードを書いていきます! main.js hello(); function hello (){ console.log('こんにちは') } 上記の例は関数の定義前に関数の実行を行ってます。 本来コードをは上から読み込まれていくので変な感じがしますよね。。。 コンテキスト内で宣言した関数や変数は実行前にメモリに保存されるので先に実行されても問題なく実行することができます! varとlet・constの違い 次にvarを使用した時とlet・constを使った時の挙動の違いについてです! main.js console.log(b); // undifinedが出力 var b= 0 ; console.log(b); // 0が出力 console.log(c) //Uncaught ReferenceError: Cannot access 'c' before initialization at let c = 100 何が違うのか varはundifinedの初期値が設定されてる為エラーが発生しない 逆にいうとletとconstに関してはundifindedの初期値が設定されてない為、エラーが表示されるという仕組みです! 基本はletとconstを使用すれば問題ないかと思います! 無名関数(関数式)の場合 気付いてる方もいるかも知れませんが、無名関数は変数の形をとっていますよね! const a = (){}これって。。。 変数の形と一緒ですよね! なので関数式を使用する場合はundifinedが初期値と設定されてないので main.js bye() //Uncaught ReferenceError: Cannot access 'bye' before initialization const bye = function(){ console.log('byebye') } エラーが出てしまいますね! なのでbye()の位置を関数下で呼び出すことでエラーなく表示することができます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript基礎メモその4

オブジェクトと関数 オブジェクトの『値』の部分には、関数を用いることができる。 const 定数名 = { プロパティー名: () => { 処理 } }; // 関数の呼び出し 定数名.プロパティ名(); 『定数名.プロパティ名();』で関数を呼び出す。 const user = { name: "dai", greet: () => { console.log("こんにちは"); } }; user.greet(); 結果 こんにちは クラス クラスはオブジェクトの設計図のようなもので、似たオブジェクト効率よく作成できる class User { } クラス名は大文字から始める。 クラスは末尾に『;』は付けない インスタンス インスタンスはクラスから生成されたオブジェクトのことをいう。 class Animal { } // const animalにAnimalインスタンスを代入 const animal = new Animal(); console.log(animal); 結果 Animal{} コンストラクタ クラスにはコンストラクタと呼ばれる機能が用意されている。 コンストラクタはインスタンス生成する時に実行したい処理や設定を追加するための機能である。 クラス内に『constructor() {}』と記述する。 class Animal constructor() { } } class Animal { constructor() { console.log("こんにちは"); } } const animal1 = new Animal(); const animal2 = new Animal(); 結果 こんにちは こんにちは コンストラクタにプロパティと値を追加する コンストラクタの中で『this.プロパティ = 値』とすることで、生成されたインスタンスにプロパティと値を追加できる。 class Animal { constructor() { this.name = "dai"; } } const animal = new Animal(); console.log(animal.name); 結果 レオ コンストラクタの引数 class Animal { constructor(name, age) { this.name = name; this.age = age; } } const animal = new Animal("syo", 3); メソッド メソッドはクラスの中で定義する。『メソッド名() {}』とすることでメソッドの定義ができる。 class クラス名 { constructor() { } メソッド名 () { // 行いたい処理 } } class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } } const animal = new Animal("syo", 3); animal.greet(); メソッドを呼び出すには『インスタンス.メソッド名()』とする。 結果 こんにちは メソッド内で値を使う メソッド内でインスタンスの値を使用するには、『this』 という特殊な値を用いて、『this.プロパティ名』 とする。 class Animal { constructor(name, age) { this.name = name; this.age = age; } info() { // インスタンスのnameプロパティの値になる console.log(`名前は${this.name}です`); } } const animal = new Animal("syo", 3); animal.info(); 結果 名前はsyoです メソッド内でメソッドを使う メソッド内で 『this.メソッド名()』 とすることで、同じクラスのメソッドを使うことができる。 class Animal { greet() { console.log("こんにちは"); } info() { // 同じクラスのメソッドを実行できる this.greet(); // } } 継承 新しく作成するクラスが既存のクラスの一種である場合、『継承』という方法を用いることで効率よく作成できる。 『継承』とは、既にあるクラスを元に、新しくクラスを作成する方法のことである。 class Dod extends Animal { } 継承を用いてクラスを作成するには『extends』を用いる。 上のコードでは、『Animalクラス』を継承して「Dogクラス』を作成している。 継承したクラスを使う 『Dogクラス』は『Animalクラス』の全ての機能を引き継いでいる。 下のコードのように『Dogクラス』に定義されている『infoメソッド』などを使用することができる。 class Animal { info() { console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Dog extends Animal { } const dog = new Dog("ken", 4); dog.info(); // Animalクラスに定義されているメソッドを使用できる 結果 名前はkenです 4歳です メソッドの戻り値 メソッドでは、関数と同じようにも踊り地を用いることができる。 class Dog extends Animal { getHumanAge() { return this.age *7; // メソッドでも戻り値は使える } } const dog = new Dog("gen", 4); const humanAge = dog.getHumanAge(); console.log(humanAge); 結果 28 子クラスで定義した独自メソッドは、親クラスから呼び出すことはできない。 オーバーライド 親クラスと同じ名前のメソッドを子クラスに定義すると、子クラスのメソッドが優先して使用される。 メソッドと同じように、コンストラクタもオーバーライドすることができる。 子クラスにプロパティを追加したい場合などに使用する。 コンストラクタをオーバーライドする際は1行目に『super()』と記述する必要がある。 class 子クラス extends 親クラス { constructor() { super(); //子クラスのコンストラクタの処理 } } 子クラスのコンストラクタ内の『super()』では、その部分で親クラスのコンストラクタを呼び出している。 親クラスのコンストラクタが引数を受け取る場合には、『super』の後ろの丸括弧『()』に引数を渡す必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 環境ならどこでも使えそうな短いコードの日付書式出力関数。コピペ用。

日付ライブラリなら、大体定番なのは Moment.js なので入れてる方も多いかもしれません。 あるいは、Momentがもう更新されないことを知っている方なら、Day.js とかでしたっけ。いろいろ処理するのは便利ですよね。標準的なものがコロコロ変わるのはJavaScriptの楽しめるところですが、開発はなかなか大変です。 そういう日付系ライブラリ入れていれば書式指定の日付出力は簡単なので、こういうコードもいらないのですが、 単に書式出力する程度のことなら、ライブラリ入れずにコードコピペで済ましたい場合も多々あるかと思います。(というか多々あった) そういうときのためにコードコピペ用に書きました。 const dateToString = (format, date) => { const padFirstZero = (value) => { return ('0' + value).slice(-2); } const YYYY = date.getFullYear(); const YY = date.getFullYear().toString().slice(-2); const M = (date.getMonth() + 1).toString(); const MM = padFirstZero(M); const D = date.getDate().toString(); const DD = padFirstZero(D); const H = date.getHours().toString(); const HH = padFirstZero(H); const m = date.getMinutes().toString(); const mm = padFirstZero(m); const S = date.getSeconds().toString(); const SS = padFirstZero(S); const DDD = ['Sun', 'Mon', 'Tue', 'Wed', 'Thr', 'Fri', 'Sat'][date.getDay()] switch (format) { case 'YYYY-MM-DD': return `${YYYY}-${MM}-${DD}`; case 'D-M-YY': return `${D}-${M}-${YY}` case 'YYYY/MM/DD HH:mm:SS(DDD)': return `${YYYY}/${MM}/${DD} ${HH}:${mm}:${SS}(${DDD})` default: throw new Error(`dateToString args:format(=${format}) is not supported.`); } }; console.log(dateToString('YYYY-MM-DD', new Date())); console.log(dateToString('D-M-YY', new Date())); console.log(dateToString('YYYY/MM/DD HH:mm:SS(DDD)', new Date())); コード読みやすいと思いますので、拡張するときもどうぞ。曜日出力日本語とか。 もっと複雑多機能なやつ 一度書いたものは二度と書くときに迷いたくないし、将来どこかで再利用しやすいようになるべく汎用的にを考えて実装した、自作ライブラリのParts.js の datetimeToString にはより多機能な日付書式出力関数は用意しているのですがコード量が半端なく大きいのでコピペ不向きでした。 partsjs/date.test.js at v10.5.0 · standard-software/partsjs https://github.com/standard-software/partsjs/blob/v10.5.0/source/date/date.test.js#L315 以前、記事にもしているのでご参考にでもどうぞです。 JavaScript 書式を指定して日時出力 和暦対応 引用囲み対応 - Qiita https://qiita.com/standard-software/items/f56a05d35f96022a08a9
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon API Gatewayを使ってリアルタイムkintone

kintoneの「一覧をリアルタイムで更新したい」,「同レコードを複数人同時に編集したい」と感じることはありませんか? Amazon API GatewayのWebSocket APIを利用すると,他ユーザーの操作をリアルタイムで検知して,上述機能を作成できます. しかも超簡単,お手頃価格で. デモ 一覧をリアルタイム更新するデモです. 左がレコード一覧を見ている人,右がレコードを追加編集している人です. 右の人がレコード操作すると,左の人の一覧が勝手に更新されます. 実装方法 WebSocketサンプルアプリのデプロイ AWSがWebSocketのサンプルアプリを用意してくれているので,そのまま利用します. AWSアカウントがない方は,AWS アカウント作成の流れを参考に作成してください. アカウントを作成したら,simple-websockets-chat-appにアクセスします. アクセスしたら,右上にあるDeployボタンをクリックします. Deployボタンを押すと,simple-websockets-chat-appの設定とデプロイ画面に遷移します. 右下の方にある,「このアプリがカスタム IAM ロールとリソースポリシーを作成することを承認します。」にチェックを入れ,デプロイボタンを押します. デプロイボタンを押すとデプロイが開始します. WebSocket URLのメモ デプロイが完了したらWebSocket URLをメモします. ヘッダーの検索バーからAPI Gatewayを検索してクリックします. API Gatewayを開いたら,「SimpleChatWebSocket」をクリックします. サイドバーのダッシュボードをクリックして,ページ上部に表示されたWebSocket URLをメモします. kintoneの設定 カスタマイズビューHTML(一覧名: リアルタイム更新) <div id="async-view"></div> JavaScript 下記を順に読み込みます. https://js.cybozu.com/kintone-rest-api-client/1.12.1/KintoneRestAPIClient.min.js https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/js/iziToast.min.js AsyncTable.js sample.js ・AsyncTable.js class AsyncTable { constructor(columns) { this.columns = columns; this.table = document.createElement('table'); this.thead = document.createElement('thead'); this.tbody = document.createElement('tbody'); this.table.appendChild(this.thead); this.table.appendChild(this.tbody); this.table.classList.add('async-table'); } getTable () { return this.table; } setFormFields (formFields) { this.thead.innerHTML = ''; const tr = document.createElement('tr'); tr.innerHTML = '<th>$id</th>'; this.columns.forEach((column) => { const th = document.createElement('th'); th.innerText = formFields.properties[column].label; tr.appendChild(th); }); this.thead.appendChild(tr); } createRowContent (record) { const fragment = document.createDocumentFragment(); this.columns.forEach((column) => { const td = document.createElement('td'); td.innerText = record[column].value; fragment.appendChild(td); }); return fragment; } createRow (record) { const id = record.$id.value; const tr = document.createElement('tr'); tr.id = `record-${id}`; tr.innerHTML = `<td><a href="show#record=${id}">${id}</a></td>`; tr.appendChild(this.createRowContent(record)); return tr; } updateRow (record) { const id = record.$id.value; const tr = document.getElementById(`record-${id}`); tr.innerHTML = `<td><a href="show#record=${id}">${id}</a></td>`; tr.appendChild(this.createRowContent(record)); return tr; } focusRow (tr) { tr.classList.add('focused'); setTimeout(() => tr.classList.remove('focused'), 5000); } setInitialRecords (records) { const _this = this; this.tbody.innerHTML = ''; const fragment = document.createDocumentFragment(); records.forEach((record) => { fragment.appendChild(_this.createRow(record)); }); this.tbody.appendChild(fragment); } addRecord (record) { const tr = this.tbody.insertBefore(this.createRow(record), this.tbody.firstChild); this.focusRow(tr); } updateRecord (record) { const tr = this.updateRow(record); this.focusRow(tr); } } ・sample.js 2行目の「WebSocket URL」と6行目の「一覧に表示するフィールドのフィルドコード」は適宜変更してください. (() => { const url = 'wss://*****.execute-api.ap-northeast-1.amazonaws.com/Prod/'; // WebSocket URL kintone.events.on([ 'app.record.index.show' ], (event) => { if (event.viewName !== 'リアルタイム更新') return event; const columns = [// 一覧に表示するフィールドのフィルドコード '品名', '値段', ]; const containerId = 'async-view'; const client = new KintoneRestAPIClient(); const asyncTable = new AsyncTable(columns); kintone.Promise.all([ client.app.getFormFields({ app: kintone.app.getId() }).then((formFields) => { asyncTable.setFormFields(formFields); }), client.record.getAllRecordsWithCursor({ app: kintone.app.getId() }).then((records) => { asyncTable.setInitialRecords(records); }), ]).then(() => { document.getElementById(containerId).appendChild(asyncTable.getTable()); }); const socket = new WebSocket(url); socket.addEventListener('message', (e) => { const sentEvent = JSON.parse(e.data); if (sentEvent.type === 'app.record.create.submit.success') { iziToast.info({ title: 'レコードが追加されました', message: `$id: ${sentEvent.record.$id.value}`, }); asyncTable.addRecord(sentEvent.record); } if (sentEvent.type === 'app.record.edit.submit.success') { iziToast.info({ title: 'レコードが更新されました', message: `$id: ${sentEvent.record.$id.value}`, }); asyncTable.updateRecord(sentEvent.record); } }); return event; }); kintone.events.on([ 'app.record.create.submit.success', 'app.record.edit.submit.success' ], (event) => new kintone.Promise((resolve) => { const socket = new WebSocket(url); socket.addEventListener('open', () => { socket.send(`{"action": "sendmessage", "data": ${JSON.stringify(JSON.stringify(event))}}`); resolve(event); }); }) ); })(); CSS 下記を順に読み込みます. https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/css/iziToast.min.css AsyncTable.css ・AsyncTable.css .async-table{ width: 100%; } .async-table tbody tr:nth-child(2n+1){ background: #f5f5f5; } .async-table th, .async-table td{ border: 1px solid #e3e7e8; padding: 10px; font-weight: normal; text-align: left; } .async-table tbody tr.focused{ background: #9ddeff; } 設定は以上です. 気が向いたら,同レコードを複数人同時に編集の記事も書こうかと思います.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cesiumと国土地理院標高タイルで3D地図をつくろう

サンプルサイト はじめに 以前にdeck.glと標高タイルで3D地図をつくりました。国土地理院が配信している標高タイルの有用性は上記の記事で述べたところです。今回は3D地図のスタンダードであるCesiumで、同様に標高タイルを活用した3D地形表現に挑戦してみました。 CesiumのTerrain CesiumはインストールすればAPI-keyだとかがなくても衛星写真と地形データを使わせてくれます(しかし画面内にAPI-keyが設定されていない旨は表示されます)。なのでそれを使わせてもらえば良いと言えば良い…しかし都市部の精度は高くないし、単一のエコシステムに依存してしまうと、いつまで同じように使えるのかなんて不安もある訳です(どこかで聞いた話ですが)。 CesiumのTerrainは、何やらむずかしそうなツールを使えば作れるみたいです。データ自体は.terrainファイルなるタイル化されたバイナリデータです。なので、DEMがあれば多分この「正攻法」の手法で地形データは生成出来るでしょう。 標高タイルが使えるとうれしい 以前の記事の繰り返しですが、既にホスティングされていて、自由に使える標高タイルを使いたい!(当然、出典の明記が必要です) cesium-gsi-terrain という訳で前回同様作りました、npmからインストールできます。 npm install cesium-gsi-terrain import GsiTerrainProvider from 'cesium-gsi-terrain'; // かなり端折ってます const viewer = new Viewer(canvas, { terrainProvider: new GsiTerrainProvider({}), }); ホントのところ terrain-RGBをCesiumで読み込む神モジュールがすでに存在していたので、それをフォークして標高値の計算方法を標高タイルのルールに合わせてあげただけです。先人に感謝…! 終わりに deck.glでは、標高データとテクスチャのズームレベルが常に一定である必要があるらしく、テクスチャの解像度の上限が標高タイルの最大ズームレベル14になってしまっていましたが、Cesiumではそういう事はなく、高解像度のテクスチャを貼ってくれます。カメラの傾きもかなり自由度が高く、こと3D表現に関してはやはりCesiumが強いなぁと思うところです。地図アプリ制作にあっては、それぞれの強みを理解したうえで、適切にライブラリを選定する必要がありますね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Javascript】Javascript/Cssでモーダルを作ってみた

初めに ボタンを押したら周りの背景が薄暗くなり、画面の真ん中に出てくるウィンドウが気になり、javascriptとcssを使って実装してみました。また、自分なりに学習し学んだ内容を記事にしてみました。 ※内容に間違いなどがある場合はご指摘をよろしくお願いします。 モーダルとは 英語でmodal。「様式の」「形態の」の意味で、ボタンを押したらブラウザ上に現れるウィンドウのこと。閉じるボタンを押すまで他の操作ができなくなる画面のこと。 実装における主な概念 ❶HTMLのタグとCSSのz-indexを利用し3つの層を作る ❷overlayという名前のタグを作り、ベースとなる画面を被せる薄暗いレイヤーを用意する ❸ボタンを押した時に画面の一番手前に出てくるmodalという名前のタグを作る。 ❹hiddenというクラス名をmodalタグやoverlayタグにあらかじめ付けておく ❺cssでhiddenの属性を「display: none」にセットする ❻Javascriptでボタンを押した際に「hidden」タグを付けたり外したりする処理をする 3つのレイヤの構成 イメージとしてはベースがあってその上にoverlay、modalが重なる感じ。overlayとmodalはボタンを押したときに現れ、閉じるボタンを押すと隠れる。 HTML側の実装 modalとoverlayというクラス名を持つ2つのタグを用意します。また、2つのタグにはhiddenというクラス名を別途付けておきます。 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <link rel="stylesheet" href="style.css" /> <title>モーダルウィンドウ</title> </head> <body> <div class="overlay hidden"></div> <!-- overlay --> <button class="show-modal">モーダルボタン</button> <!-- モーダルを表示されるボタン --> <div class="modal hidden"> <!-- モーダル--> <button class="close-modal">&times;</button> <!-- モーダルを閉じるボタン --> <p> モダールウィンドウです。 </p> </div> <script src="script.js"></script> </body> </html> CSSの実装 * { /* ブラウザ全体の余白を消す */ margin: 0; padding: 0; } body { font-family: sans-serif; height: 100vh; position: relative; /* body要素にflexboxを指定する */ display: flex; align-items: flex-start; justify-content: center; background: linear-gradient(to top left, #f76060, #ebd407); /* body要素の背景をグラデーションにする */ } .show-modal { font-size: 20px; /* modalを開くボタン */ font-weight: 600; padding: 10px 30px; border: none; margin-top: 100px; background-color: #fff; color: #444; border-radius: 20px; cursor: pointer; } .close-modal { position: absolute; /* modalを閉じるボタン */ top: 4px; right: 22px; font-size: 40px; color: #333; cursor: pointer; border: none; background: none; } p { font-size: 20px; } .hidden { display: none; /* hiddenクラスが付与されている場合は表示させないようにする */ } .modal { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 70%; color: rgb(255, 0, 0); background-color: white; padding: 6rem; border-radius: 5px; box-shadow: 0 10px 10px rgba(0, 0, 0, 0.3); /* modalに影を作る */ z-index: 10; /* z-indexでmodalを一番上に位置させる */ } .overlay { position: absolute; /* overlayの絶対位置を左上を起点にし幅と高さを100%にする */ top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); backdrop-filter: blur(3px); /* 一番下のbody領域をぼやかす */ z-index: 5; /* z-indexの数字を0と10の間の数字にし、bodyとmodalの真ん中に位置させる */ } bodyとmodal、overlayのz-indexをそれぞれ0,5,10にします。こうすることによってbody要素が一番下、その上にmodalウィンドウがあり、一番上がoverlayという層になります。また、hiddenクラスのdisplayをnoneにしておき、このクラスが付与された時に要素が見えないようにします。 Javascriptの実装 "use strict"; const modal = document.querySelector(".modal"); //modalを指定 const overlay = document.querySelector(".overlay"); //overlayを指定 const btnOpenModal = document.querySelector(".show-modal"); //modalを開くボタンを指定 const btnCloseModal = document.querySelector(".close-modal"); //modalを閉じるボタンを指定 //modalとoverlayのhiddenクラスを消す(modalとoverlayが見えるようにする)処理 const openModal = () => { modal.classList.remove("hidden"); overlay.classList.remove("hidden"); }; //modalとoverlayのhiddenクラスを追加する(modalとoverlayが見えないようにする)処理 const closeModal = () => { modal.classList.add("hidden"); overlay.classList.add("hidden"); }; //modalの開くボタンと閉じるボタンをクリックした時の処理 btnOpenModal.addEventListener("click", openModal); btnCloseModal.addEventListener("click", closeModal); modal,overlay,モーダルの開くボタン、閉じるボタンをquerySelectorで指定し、それぞれの要素にhiddenクラスを付けたり、外したりしています。また、addEventListenerでclickした場合にボタンの処理を行うようにしています。 結果物 ①モーダルを開く前 ②開いた場合 参考サイト https://goworkship.com/magazine/modal-windows-mobileui/ https://developer.mozilla.org/ja/docs/Web/CSS/backdrop-filter
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PAY.jpエラーハンドリング用 PAY.jpのstatusで受け取る error一覧

Error エラーハンドリング例 レスポンス例を記載 { "error": { "message": "Unrecognized request URL: GET /v1/not_found", "status": 404, "type": "client_error" } } エラーとなるリクエストには、"error"というキーが含まれたエラーレスポンスが返されます。 エラーレスポンス “error” 内の “status” にはHTTPステータスコードと同様の値が入ります (下表)。 エラーレスポンス “error” 内の “message” には以下のエラーメッセージが含まれます。 error[type] 詳細 client_error リクエストエラー card_error カードに関するエラー server_error PAY.JPや決済ネットワーク側のエラー not_found_error 存在しないオブジェクト not_allowed_method_error 許可されていないメソッドエラー auth_error 認証エラー invalid_request_error 無効なリクエスト error[code] 詳細 invalid_number 不正なカード番号 [deprecated] invalid_cvc 不正なCVC [deprecated] invalid_expiration_date 不正な有効期限年、または月 [deprecated] incorrect_card_data いずれかのカード情報が誤っている [2020/12/10以降新設] invalid_expiry_month 不正な有効期限月 invalid_expiry_year 不正な有効期限年 expired_card 有効期限切れ card_declined カード会社によって拒否されたカード card_flagged カード情報の誤入力が続いたことによる一時的なロックアウト processing_error 決済ネットワーク上で生じたエラー missing_card 顧客がカードを保持していない unacceptable_brand 対象のカードブランドが許可されていない invalid_id 不正なID no_api_key APIキーがセットされていない invalid_api_key 不正なAPIキー invalid_plan 不正なプラン invalid_expiry_days 不正な失効日数 unnecessary_expiry_days 失効日数が不要なパラメーターである場合 invalid_flexible_id 不正なID指定 invalid_timestamp 不正なUnixタイムスタンプ invalid_trial_end 不正なトライアル終了日 invalid_string_length 不正な文字列長 invalid_country 不正な国名コード invalid_currency 不正な通貨コード invalid_address_zip 不正な郵便番号 invalid_amount 不正な支払い金額 invalid_plan_amount 不正なプラン金額 invalid_card 不正なカード invalid_card_name 不正なカードホルダー名 invalid_card_country 不正なカード請求先国名コード invalid_card_address_zip 不正なカード請求先住所(郵便番号) invalid_card_address_state 不正なカード請求先住所(都道府県) invalid_card_address_city 不正なカード請求先住所(市区町村) invalid_card_address_line 不正なカード請求先住所(番地など) invalid_customer 不正な顧客 invalid_boolean 不正な論理値 invalid_email 不正なメールアドレス no_allowed_param パラメーターが許可されていない場合 no_param パラメーターが何もセットされていない invalid_querystring 不正なクエリー文字列 missing_param 必要なパラメーターがセットされていない invalid_param_key 指定できない不正なパラメーターがある no_payment_method 支払い手段がセットされていない payment_method_duplicate 支払い手段が重複してセットされている payment_method_duplicate_including_customer 支払い手段が重複してセットされている(顧客IDを含む) failed_payment 指定した支払いが失敗している場合 invalid_refund_amount 不正な返金額 already_refunded すでに返金済み invalid_amount_to_not_captured 確定されていない支払いに対して部分返金ができない refund_amount_gt_net 返金額が元の支払い額より大きい capture_amount_gt_net 支払い確定額が元の支払い額より大きい invalid_refund_reason 不正な返金理由 already_captured すでに支払いが確定済み cant_capture_refunded_charge 返金済みの支払いに対して支払い確定はできない cant_reauth_refunded_charge 返金済みの支払いに対して再認証はできない charge_expired 認証が失効している支払い already_exist_id すでに存在しているID token_already_used すでに使用済みのトークン already_have_card 指定した顧客がすでに保持しているカード dont_has_this_card 顧客が指定したカードを保持していない doesnt_have_card 顧客がカードを何も保持していない already_have_the_same_card すでに同じカード番号、有効期限のカードを保持している invalid_interval 不正な課金周期 invalid_trial_days 不正なトライアル日数 invalid_billing_day 不正な支払い実行日 billing_day_for_non_monthly_plan 支払い実行日は月次プランにしか指定できない exist_subscribers 購入者が存在するプランは削除できない already_subscribed すでに定期課金済みの顧客 already_canceled すでにキャンセル済みの定期課金 already_paused すでに停止済みの定期課金 subscription_worked すでに稼働している定期課金 cannot_change_prorate_status 日割り課金の設定はプラン変更時のみ可能 too_many_metadata_keys metadataキーの登録上限(20)を超過している invalid_metadata_key 不正なmetadataキー invalid_metadata_value 不正なmetadataバリュー apple_pay_disabled_in_livemode 本番モードのApple Pay利用が許可されていない invalid_apple_pay_token 不正なApple Payトークン test_card_on_livemode 本番モードのリクエストにテストカードが使用されている not_activated_account 本番モードが許可されていないアカウント too_many_test_request テストモードのリクエストリミットを超過している payjp_wrong PAY.JPのサーバー側でエラーが発生している pg_wrong 決済代行会社のサーバー側でエラーが発生している not_found リクエスト先が存在しないことを示す not_allowed_method 許可されていないHTTPメソッド over_capacity レートリミットに到達 refund_limit_exceeded 期限を過ぎた後の返金操作 error[status] Meaning 200 リクエスト成功 400 不正なパラメーターなどのリクエストエラー 401 APIキーの認証エラー 402 カード認証・支払いエラー 404 存在しないAPIリソース 429 レートリミットによるアクセス一時拒否 500 PAY.JPや決済ネットワークでの障害
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React&Javascript&Typescript基礎の備忘録

まず初めに Reactでウェブアプリを作る際に、Typescript&Javascriptはちゃんと勉強しておかないと、バグに直面した際 React特有の書き方で間違った書き方をしている 1 Javascript or Typescriptの書き方が間違っている 2 のどちらで困っているのか、初心者には判別がつかず、バグに対して手が付けられなくなる。 Javascript Javascriptの基礎を学べるサイト JSONの細かい仕様 Typescript Typescriptの基礎を学べるサイト このサイトは包括的に情報がまとまっており、素晴らしい。 React TypeScriptでReactに入門するチュートリアル こちらのサイトでは、下の画像のようなテーマパークの入場料計算を行うWebアプリケーションを作成しています。 create-react-appで React + Typescript な環境を構築する Reactでweb開発を行うための事始めのやり方が記載されている。 hooks & redux react router axios ブラウザや node.js で動く Promise ベースの HTTP クライアントである。 REST-API を実行したいときなど、これを使うと実装が簡単にできる。 番外編 ReactとFirebaseを使っていろんなサイトを作る React React+TypeScriptを使ったNetflixの映画一覧を表示するアプリケーション git パスワードを毎回聞かれる問題をHTTPSでも解決 API開発・テスト便利ツール Postmanの使い方メモ Node.jsが動くための環境構築 CircleCI Docker npmとyarnの違い
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 超初心者ガイド

先日、会社の新人の方向けの学習素材として「JavaScript 超初心者」なるものを作成しました!筆者自身もJS勉強中の身ではありますが、何かの参考になればという事でこちらにも記載させていただきました。 このJavaScript 超初心者ガイドでは、そもそもJavaScriptとは何なのか、またJavaScriptを使った簡単なコーディングが出来るように解説をしていきたいと思います。(一応本ガイドを書いている私自身、まだまだJavaScript勉強中の身でありますので「ここの解釈おかしいよ」、「ここ間違ってるよ」等あれば随時ご指摘いただけますと幸いです!) JavaScriptってそもそも何?? さっそくですが「JavaScriptってそもそも何なの?」という部分からお話をさせてください。JavaScriptって、単体でそれがなんなのか説明がしづらいので以下では一旦その他のHTMLやCSSが担っている役割などと合わせて解説してみます。 HTML、CSS、JavaScriptを人の体に置き換えて考えてみよう! HTMLやCSS、JavaScriptがそれぞれどんなものか考えるとき、個人的にはそれぞれ人の体のパーツにあてはめて考えるとすごくイメージがしやすいと思っています。具体的には以下の図のような関係性になっています。 そもそもHTMLやCSSはWebサイトを作るときに使う言語なのですが、JavaScriptもそれらと同様に「Webサイトを作る際に使用する言語」だと考えてもらえればOKです! 当然ですがそれぞれの言語にはそれぞれの役割があり、そちらに関しては↑のイラストで示した通りとなっています。 HTML:Webサイトの骨組み、骨格を作る役割をになっています。  CSS:Webサイトの見た目を決める役割を担っています。 では肝心の、「じゃあJavaScriptは人の体で表すとどうなるの??」という部分ですが、 JavaScriptは「しゃべる、歩く、食べる」など動作を付与する役割を担っています。 いまいちイメージしづらいようであれば「JavaScriptはWebサイトに動きを付けてあげる役割」をしていると覚えてあげてください。 実際にJavaScriptを使ってWebサイトに動きを付けてみよう! いきなり!?と思うかもしれませんが、百聞は一見にしかずということですごく簡単な機能をJavaScriptを使って作ってみましょう! 以下今回つくってみる機能の説明から順序立てて説明をしていきます。 1. 今回作る機能 ー ボタンをクリックすると背景色を変更できるようにしよう! ー 今回はJavaScriptを使って、ページ内のボタンをクリックするとページ全体の背景色が変わるような機能を作ってみたいと思います。(完成品はこちら https://jsfiddle.net/6jf1qwxs/2/ ) 「Click Here!」をクリックすると、、 一瞬で背景色が変わりました! 2. 作る前の下準備 上記で説明したようなページを作るにはテキストエディタ(VSCode、Sublime etc...)が必要となりますが、制作環境が手元にない方は以下のようなウェブ上でコーディングができるサービスを利用しましょう! このガイドでも実際に以下のJSFiddleを利用することを想定して解説を進めていきます。 JSFiddle(オススメ!) - https://jsfiddle.net/ Codepen - https://codepen.io/ HTMLとCSSをコピペ 本ガイドではJavaScriptの解説を中心に進めていきますので、一旦HTML、CSSについては解説は行いません。まだHTML、CSSの理解がまだちょっと、、という方も今回は一旦以下の手順に従ってHTMLとCSSはコピペして作業をすすめましょう。 今回使用するHTML、CSSは以下の通りです。 HTML <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body class="bg"> <p class="button"> Click Here! </p> </body> </html> CSS .bg { display: flex; height: 100vh; align-items: center; justify-content: center; } .button { display: inline-block; padding: 10px 20px; color: #fff; font-weight: 700; background: #369cd6; border-radius: 100px; } JSFiddleにアクセスすると、HTML、CSS、JavaScriptを記入できるページが立ち上がるので、上記のコードを以下の図の通り、それぞれHTML、CSSの記入欄にコピペをしてみてください! コピペが完了したらCntrl+Sを押して保存してみましょう。すると以下のようにボタンを含んだページが完成します。 3. 実際にJavaScriptを書いてみよう! さぁ準備は整いました!あとは実際にJavaScriptを記述してきましょう! ここではまず前段として、JSFiddleのようなオンラインのテキストエディタを使わずに実際の案件などでJavaScriptを記述するときの記載方法をご紹介します。そしてその後基本的な文法などの解説を進めていきます。 実際の案件ではJavaScriptはどこにどうやって書くの?? お気づきかもしれませんが、当然上記でご紹介したJSFiddleのようなサービスは実際の案件で使うことができません。 では実際の案件ではJavaScriptはどこに、どうやって記述をするのでしょうか。 1. HTMLファイル内に直接記述する JavaScriptを記述する方法の1つは、HTMLファイルに直接記述するという方法です。 HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body class="bg"> <p class="button"> Click Here! </p> <script> /* HTMファイルの中にJavaScriptを記述するときは、このようにScriptタグの間に書いてあげましょう! */ </script> </body> </html> 上記のコードをみるとわかるかと思いますが、HTMLファイルに直接JavaScriptを書く際は必ず「ここからJavaScriptの記述が始まるよ!」という目印のため(scriptタグ)の間にコードを記述します。また記述場所についてはHTMLファイル内のbody要素の一番下に記載をしましょう。 記述方法:scriptタグの間にコードを記述 記述場所:bodyタグの一番下 2. JavaScriptファイルを作成しHTMLファイル内で読み込む JavaScriptを記述する方法の2つ目は、外部で作成したJavaScriptファイルをHTMLファイル内で読み込むというもので一般的にはこの方法が一番メジャーな方法かと思います。 外部のCSSファイルを読み込むのと同様で、HTMLファイル内に読み込みたいJavaScriptファイルのパスを記載した記述を追加します。 HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body class="bg"> <p class="button"> Click Here! </p> <script src="ファイルのパスを記述します"></script> </body> </html> 上記のコードを参照すると、1で紹介した方法と同じくscriptタグを使用することがわかると思います。ただ、外部ファイルを読み込む際は、src="●●"部分に読み込みたいJavaScriptファイルのパスを記入する必要があります。 記述方法:scriptタグ src部分にファイルパスをいれて記述 記述場所:bodyタグの一番下 以上実際の案件などでJavaScriptを使用する際の記述方法をご紹介しました。 本ガイドではJSFiddleのJavaScript記入欄にコードを記入する形で作業を進めていくため、実際に外部ファイルを用意したり、HTML部分にscriptタグを使って記述を追加する必要はありませんのでご安心ください! JavaScriptの文法 本ガイドで最も重要といってもいいパートに入ってきました。文法と聞くと途端に「うっ..」と感じられる人もいるかもしれませんが、大丈夫ですそこまで複雑な内容ではありません! ではまず文法とは?というところからですが、 本ガイド冒頭でもご紹介したようにJavaScriptは「Webサイトを作る際に使用する言語」です。言語ということは、日本語や英語と同じように文法という一定のルールが存在します。 ではこれから作る機能を使って実際にJavaScriptの文法がどのような構造になっているのか見てみましょう。 やばいですね。めっちゃむずそうです。 このよくわからない英語の羅列だと途方に暮れちゃうので、それぞれがどんな役割をしているかをまず見てみましょう! すごく難しそうに見えたコードですが、実際に書いているのは「何を・どうすると・どうなる」という一連の動作を書いているだけなんです。上記のコードをこの構造に従ってさらにシンプルに直したものが以下のものです。 すごく見やすくなりましたね。 ではここで次のステップに進む前に一度要点をまとめます! ----------【まとめ】---------- どんなに難しく見えるコードも基本的な構造は上記の「何を・どうすると・どうなる」であることに変わりはありません! 実際に自分でコードを記述する際は、作ろうとしている機能を一旦上記3つのパーツに分解してみましょう。そうすることで各パーツごとにどういう記述をする必要があるのかが明確になり混乱するのを防いでくれます。 ------------------------- では次に各パーツ(何を・どうすると・どうなる)についてより突っ込んでみていきましょう! 1. 何を 今一度作成する機能がなんだったか思い出してみましょう。 今回作ろうとしているのは「ボタンをクリックすると背景色を変更できるようにしよう!」という機能でしたね。 もう気が付いた方もいるかもしれませんが、この「何を」の部分には動作の対象になる要素を入れてあげる決まりとなっています。 今回の例でいうと「クリックする」という動作の対象は当然ボタンですよね。そのためここにはボタンを指す「.button」という記述をいれています。 ただここで「ちょっとまって!もともとのコードにあった、document.querySelector~みたいな訳の分からない記述はなんなの?!」という疑問が出てくるかと思います。 たしかに意味わかんないですよね、順を追ってご説明します。 まず初めに「document.querySelector('.button')」が何を意味しているかについてお話しすると、このコードでは「何を」にあたるHTMLファイル内のボタン部分を、コンピューターが理解できる形で記述をしてあげているんです。 コンピューターは私たちとは違って日本語や、英語ではない別の言語をしゃべっている人だと思ってあげてください。その人に「ボタン」とか「Button」という私たちの言葉で指示を出してもわからないですよね。(下のイメージみたいな状況) なのでここではコンピューターでも理解できるJavaScriptで以下のような指示を出しています。 そうです、ここでは単純に「HTMLファイルの中から、『.button』というクラス名の要素を探してきて!」という命令をだしているんです。 (「.button?クラス名?」となった方も安心してください、基本的なCSSの内容がわかればすぐに意味は理解できるので読み終わってからGoogleで「CSS、クラス名」とかで検索してみてください) 実際に序盤でコピーしてもらったHTMLのボタン部分を見てもらうと、 HTML <p class="button">  Click Here! </p> HTMLの記述の中に「class="button"」という、クラス名を付けてあげる記述があるのがわかります。 JavaScriptで「何を」を指定する際には、今ご紹介したようにクラス名などの何か目印となるものをHTMLファイルから探してという命令をコンピューターに出すという方法で指定を行います。 そうすることで以下のイメージのようにコンピューターは私たちが何に対してアクションをかけようとしているのか理解できるようになります。 この「コンピューターが理解できるような形で記述」をするという考え方は結構大事で、一見ややこしそうに見える記述でも「コンピューターに読みとってもらえるようにこうやって書いてるんだな」と考えるだけで納得しやすくなったりします。ぜひ頭の片隅に置いておいてくださいね。 ----------【まとめ】---------- 「何を」では、アクションを起こす対象について記述します。(例:ボタンをクリックすると背景色が変わる機能 → ボタンを) 対象について記述する際は、何を指しているかコンピューターにもわかるよう、document.querySelector('CSSのクラス名')といった書き方をします。 document.querySelector('CSSのクラス名') ------------------------- 2. どうすると 「どうすると」についても、基本的な考え方は同じでここも「クリックすると」という部分をコンピューターが理解できるように記述しています。 「addEventListener」という英語の羅列だと難しく感じるかもしれませんが、要はコンピューターに「Event(クリックとか、スクロールとかの動作)をListen(感知する、注意を払っておく)しろよ!」と指示をだしているだけなんです。 でもコンピューターからすると「EventをListenするのは分かったけど、どのイベントをListenするの?」という感じなので、上記のイメージで記載したように「click」等イベントの種類をしっかりと指定してあげる必要があります。 で、このaddEventListenerのすごいところはただイベントを感知しろ!と命令するだけでなく、「そのイベントが起こったら、あらかじめ伝えておいた処理をかけろ!」というところまで指示することができるんです。 そしてこの「処理」こそが次のステップで説明する「どうなる」にあたる部分で、これについてはfunction以降の{}内に記述をする決まりとなっています。 ----------【まとめ】---------- 「どうすると」では、「何を」で指定した要素に対して起こすアクションについて記載します。(例:ボタンをクリックすると背景色が変わる機能 → クリックする) アクションの内容を記述する際は、以下のように addEventListener(' ',function( ){ }) という書き方をし、「アクションの内容」の部分にclickといった対象のアクションを記述します。 addEventListener('アクションの内容',function(){}) ------------------------- 3. どうなる さぁここまできたらもう一息です! 最後のパーツ「どうなる」の部分では「ボタンを(何を)クリックすると(どうすると)」に続く、クリックされたときに起こしたい処理の内容を記述しています。 ちなみに処理の内容は以下のようなものでした。 「背景色が変わる」 ただこれだけだと、ちょっと処理としておおざっぱすぎるのでもう少し細かく指定をしてあげましょう。 どこの背景色を変えるの? → HTMLファイル内、body要素の背景色を変えたい 具体的に何色? → 「#f1bf22」という色に変えたい いい感じですね。では上記を踏まえてもう一度処理の内容をまとめると 「HTMLファイル内、body要素の背景色が #f1bf22 に変わる」 という内容になります。 もうおなじみの流れになってきたのでなんとなく想像した方もいるかもしれませんが、あとは上記の処理内容をコンピューターが理解できるよう以下のような記述にしてあげれば完成です! また「document.body.style.backgroundColor」という長いブロックが出てきてしまったので分解しながら考えましょう。 まず「document.body」ですが、見覚えがありませんか?? そうです、「何を」ところで出てきたHTMLファイルの中から対象とする要素を指定するあの表現です!今回の場合はHTMLファイルのbody要素を指定するのでdocument.bodyと直接つなげて記述をしています。 ※補足※ なぜ「document.querySelector()」ではなく「document.body」なの?? たしかに「何を」の部分で登場したコードでは、ボタン部分を指定するのに「document.querySelector('.button')」という書き方をしていましたね。 これには理由があるのですが、ここでは便宜上以下のように考えるとわかりやすいかもしれません。 まず「document.querySelector('.button')」、「document.body」どちらもHTMLの要素を選択しようとしている点に違いはありません。 ただ対象となるHTMLの要素は違うはずです。 document.querySelector('.button') → HTML内のpタグが対象 document.body → HTML内のbodyタグが対象 そしてこのpタグとbodyタグ、HTMLのルール上大きな違いがあり、 bodyタグは各HTMLに1つしか存在できない一方、pタグは何度でもHTMLファイル内で使用ができます。 ここで考えてみてほしいのが、そもそもpタグにbuttonというクラス名を付けてあげたのはなぜでしょうか?? それはボタン部分以外にもpタグが登場した時ごちゃまぜにならないよう目印をつけてあげる必要があるからです。 そう考えると、HTMLファイルに1つしか存在しないbodyには特に目印をつける必要ってないですよね。 そしてquerySelector()という書き方は「指定したクラス名の要素をさがして!」という意味の記述でした。 そもそもクラス名という目印をつける必要のないbodyタグを指定するのであれば、あまり適切ではないですよね。 そのため、HTML内に1つしか存在できないbodyタグについてはquerySelectorなどの指示を出さずにdocument.bodyと記載するだけで指定ができてしまうんです。 「ちょっとややこしいなぁ、、」と思った方は、 bodyを指定したい! → document.body それ以外の要素を指定したい! → document.querySelector() と暗記してしまうのも全然ありだと思います! ではその後の「style.backgroundColor」について考えましょう。 背景色や文字の大きさ、幅etcなどCSSで調整をする部分に変更を加える場合は、まず「CSSに関して変更をかけますよ!」という目印の「style」と記述を行います。そのうえで具体的にCSSの何を修正するのか項目名をstyleに続けて記述を行う決まりとなっています。 今回は背景色(CSSではbackground-colorと呼ばれる部分)を変更を行うんでしたね。そのためstyle以降にはbackgroundColorと記載をしてあげましょう。 残る記述はいたってシンプルで、上記で指定した「HTMLファイル内のbody要素のbackgroundColor」を#f1bf22と設定するだけなので、続けて = '#f1bf22' と記述をおこないます。 長かったですがJavaScriptの記述は以上で終了となります!おつかれさまでした。 ではここでまた要点をまとめておきます。 ----------【まとめ】---------- 「どうなる」では、「どうすると」で指定したアクションが起きた後の処理や動作について記述します。(例:ボタンをクリックすると背景色が変わる機能 → 背景色が変わる) 処理の内容は、「どうすると」で記述した以下の addEventListener(' ',function( ){ }) 内、「処理の内容」部分に記述を行います。 addEventListener('click',function( ){ 処理の内容 }) ------------------------- まとめ かけあしではありましたが、初歩的なJavaScriptの内容についてご説明をさせていただきました! 今回流れの中で説明を省略してしまった部分等もあるので、そちらについては後日補足ページを作成予定ですのでそちらについても併せてご参照ください。 また個人的にはJavaScriptにしてもなんにしてもコードを書いてなんぼだと思うので、これからも「一緒にこの機能をつくってみよう!」という解説ページをいくつかアップできればと思いますので、ぜひ興味のある方はそちらもお暇なときに見ていただけるとより理解が深まるかと思います!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript初DOMとは何か?

DOMとは何か? DOMとは「Document Object Model」の略です。 直訳で、「ドキュメントを物として扱うモデル」。 プログラムからHTMLやXMLを自由に操作するための仕組みだ。 例えばブラウザに表示される文字の色を変更したり、大きくしたりと、Webページの見た目をプログラムで処理をしたい場合があるだろう、しかし何もしていない状態のHTMLファイルではJavaScriptから手を出す事が出来ない。そこでファイルの特定の部分に目印を付けて「この部分」に「こういう事をしたい」という処理を可能にするための取り決めがDOMである。 今回のでは「section-1」と「section-2」がID名にあたる。 ID名を指定するメソッドは以下のように定義されている。 document.getElementById(id); idはタグについているID名。 これを用いて「section-2」を取得して、その文字色を赤色に変更してみよう。 javascript document.getElementById('section-2').style.color = 'red'; このJavaScriptをブラウザで実行すると下図のようになる。 参考:エンジニアの入り口 https://eng-entrance.com/what-is-dom
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sql-jsの導入部分でハマった

Node.jsではなく、Chrome Extensionなどのブラウザ上でのjavascriptでの利用を想定。 GitHub: https://github.com/sql-js/sql.js CDN: https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.4.0/dist/sql-wasm.js 導入方法(initSqlJs) ここだけが難関でした。 外部依存のwasm(Web-Assembly...)を利用するための手続きらしいです。 このfileの先の戻り値が適切な値でないと動きません。 SQL = await initSqlJs({locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.4.0/dist/${file}`}); sql読み込み・操作 dataはbinary dataの形で引き渡します。 //dl with github rest api data = await fetch("https://api.github.com/repos/XXXXXX/XXXXXXXX/git/blobs/XXXXXXXXXXXX") .then(d=>d.json()).then(d=>atob(d.content)); //読み込み db = new SQL.Database(data) //SQLの操作 texts = db.exec("select * from texts")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebページタイトルとURLを一発でコピーできるBookmarklet

個人的によく使うBookmarkletの紹介です。 出来ること: 現在表示しているWebページのタイトルとURLをポップアップしてくれて、コピペできる。 誰かに参考ページを紹介する際になどにURLだけ貼るとぶっきらぼうな感じになりますが、タイトルをつけるとわかりやすくなったりします。 コード: (function() { window.prompt("Title+URL", document.title + "\r\n" + document.URL); })(); Bookmarklet用に改行をなくして、プロトコルとしてjavascript:を追加: javascript:(function(){window.prompt("Title+URL",document.title+"\r\n"+document.URL);})(); ブックマークバーにある適当なブックマークのURLを上記に入れ替え、そのブックマークボタンをクリックすると動作するようになります。 こんな感じで動きます。 一応、タイトルとURLを改行で分けて2段になるようにしてあります。 こんな感じで。 zamaezaaa - Qiita https://qiita.com/zamaezaaa
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GAS,JavaScript初心者でも作れる、積読管理&解消Slack bot【GAS x Slack x Spreadsheet】

はじめに 私は書店やamazonで本や雑誌をよく衝動買いしてしまいます。 しかし、「とりあえず買うけど積読してしまい、もはや買ったことすら忘れてしまう。」ということがよくあり、頭を悩ませています。 その理由として、以下の理由があると考えました。 ・もっている書籍の管理ができていない。 ・あまり興味のない本を勢いで買ってしまっている。 そこで、所持してる書籍+欲しい書籍管理用データベース、そこから定期的にリマインド、レコメンドしてくれるアプリケーションが欲しいと思ったので、Slack bot、通称Tsundokunを作りました。 実現したいこと ・書籍の管理(スプレッドシート) スプレッドシート上に以下の内容を管理する。 -書籍ID(1から順に書籍にIDを付与する) -書籍タイトル -amazonの商品ページURL -ステータス(購入済みや読破など) -データベースへの追加日時 ・Slack botへの応答内容をスプレッドシートに追加(GASxSlack) ・週一回、Slack botによるおすすめの提案(GASxSlack) 必要な前提知識 今回はGASのスクリプトのみを紹介するので、以下の内容は省略しています。 ・GAS,JavaScriptの基礎 ・GASアプリケーションのデプロイ方法 ・outgoing webhook, incoming webhookの設定方法 ・利用するチャンネルにslack botを追加方法 システム概要図 以下の画像が、ざっくりですがシステム概要図になります。 具体的にこんな動きをします。 ・欲しい本、もしくは購入した本のamazon商品ページのURLをbotを追加したslackのチャンネルに投稿する。 →入力を受け付けるとSlack botがこんな感じに応答 →入力されたデータがスプレッドシート上に入力される。 (importXML関数を用いて、amazonのURLから書籍タイトルを取得。) ・週一回、slack botがスプレッドシートにある書籍の中からランダムに1冊提案。 ・週一回、slack botが面白い本がないかを聞いてくれる。 ・コマンドを入力することで様々な機能を利用することができる。 -list(スプレッドシート上の書籍一覧をslack上に出力) -recommend(スプレッドシート上の書籍一覧よりランダムに1冊をおすすめ) -help(コマンド一覧を表示) スプレッドシートの準備 GASと連携させるSpreadsheetを新規で用意します。 今回の場合だと以下の画像のようになります。 各列の1行目にデータの名称を入れておきます。 またA列のIDには1から順に最終行まで数字を入れておきます。 加えてC列の2行目以降にはimportXML関数を入れます。 importXML関数はWEBサイトのデータをURLと条件をいれることで簡単に取得できる関数になります。 例えば2行目に入れる場合、書式は以下の通りです。 =IMPORTXML(B2,"//*[@id='productTitle']") これでamazonの商品ページから書籍のタイトルを引っ張ってくることができます。 以上でスプレッドシートの準備完了です!! (+αでできたら) 上記の関数でも十分なのですが、B列にURLが入っていないとその行のC列では「#VALUE」エラーが出てしまうので見栄えが悪いです。 iferror関数で、エラーを吐いてしまう場合には空白を表示するとすることで見た目がすっきりします。 =iferror(IMPORTXML(B2,"//*[@id='productTitle']"),"") スクリプト スクリプト全文がこちらです。 main.gs //引数に指定したtextをslackにポストする用の関数 function postSlack(text){ const webhookUrl = "https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; const options = { "method" : "POST", "headers" : {"Content-type": "application/json"}, "payload" : '{"text":"' + text + '"}', }; UrlFetchApp.fetch(webhookUrl, options); } //Slackからの入力を受け取る function doPost(e) { if(e.parameter.user_name === "slackbot") return; const data = e.parameter.text; //入力した内容によってBotの返答を分岐させる。 switch (true) { //URLを含む場合、スプレッドシートに出力 case /https.*/.test(data): postSlack("入力を受け付けたよ!面白そうな本だね!"); //スプレッドシートに内容を出力 outputToSheet(data); break; //「list」と入力した場合、showListを呼び出し case /list/.test(data): showList(); break; //「recommend」と入力した場合、recommendBookを呼び出し case /recommend/.test(data): recommendBook(); break; //「help」と入力した場合、helpを呼び出し case /help/.test(data): postSlack("↓コマンドの一覧を表示↓"); help(); break; } } //スプレッドシート用の変数定義 //現在のスプレッドシートを取得 const aBook = SpreadsheetApp.getActiveSpreadsheet(); //対象のシートを取得 const aSheet = aBook.getSheetByName("book_info_DB"); //A列の行数取得。 const rangeData = aSheet.getRange('B:B').getValues(); let lastRow = 0; for(let i=0; i<rangeData.length; i++){ if(rangeData[i][0]){ lastRow = i + 1; } } //本のタイトル,URL,ステータスの一覧を取得し配列に格納 const bookIdArray = aSheet.getRange(2,1,lastRow-1,1).getValues(); const bookUrlArray = aSheet.getRange(2,2,lastRow-1,1).getValues(); const bookTitleArray = aSheet.getRange(2,3,lastRow-1,1).getValues(); const bookStatusArray = aSheet.getRange(2,4,lastRow-1,1).getValues(); //スプレッドシートにslackでの投稿内容を出力するための関数 function outputToSheet(data) { //取得したデータを整形 const array = data.split(","); //取得したデータを整形するための正規表現を定義 const myRegeXp = "https.*[^>]"; //URLを整形 const url = array[0].match(myRegeXp); //ステータスを取得 const status = array[1]; //投稿日時を取得 const date = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd'); //整形した値と入力日時を最終行に出力 aSheet.getRange(lastRow+1,2).setValue(url); aSheet.getRange(lastRow+1,4).setValue(status); aSheet.getRange(lastRow+1,5).setValue(date); //もしステータスが未入力の場合は「未設定」を入力 if(aSheet.getRange(lastRow+1,4).getValue() === "") { aSheet.getRange(lastRow+1,4).setValue("未設定"); } } //登録している書籍一覧を「書籍名:ステータス」の形式でslack上に表示 function showList() { postSlack("リストを表示するね↓"); for(let i=0; i<bookIdArray.length; i++) { postSlack("ID:" + bookIdArray[i] + "\nTitle:" + bookTitleArray[i] + "\nURL:" + bookUrlArray[i] + "\nStatus:" + bookStatusArray[i]); } } //一週間に一度、DBの中からランダムで一冊提案する function recommendBook() { //ランダムにおすすめする本のタイトル、URLを取得 const bookNum = Math.floor( Math.random() * bookIdArray.length); const recommendBookId = bookIdArray[bookNum]; const recommendBookTitle = bookTitleArray[bookNum]; const recommendBookUrl = bookUrlArray[bookNum]; //slackに出力用の文章 const recommendMessage = "こんにちは!\n今週はこの本をを読んでみるのはどう?\nID:" + recommendBookId + "\n" + "Title:" + recommendBookTitle + "\n↓にamazonのURL貼っとくね。\n" + recommendBookUrl; postSlack(recommendMessage); } //HELP用のコマンドリスト表示 function help(){ //コマンドリストを配列に格納 const commandList = [ {command:'help', description:"Show a list of commands."}, {command:'list', description:"Show a list of books."}, {command:'recommend', description:"Recommend a book at random in the list."}, {command:'[url]', description:"Put information of books to SpreadSheet."} ] //投稿用にコマンドリストを整形 postSlack(commandList[0].command + "         " + commandList[0].description); postSlack(commandList[1].command + "           " + commandList[1].description); postSlack(commandList[2].command + "       " + commandList[2].description); postSlack(commandList[3].command + "          " + commandList[3].description); } //一週間に一度、最近面白い本があったか聞く function doMessage(e) { const message = "こんにちは!\n最近何か読みたい本見つけた?"; postSlack(message); } スクリプト解説 ではスクリプトの内容を1ブロックずつ解説していきます。 main.gs //引数に指定したtextをslackにポストする用の関数 function postSlack(text){ const webhookUrl = "https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; const options = { "method" : "POST", "headers" : {"Content-type": "application/json"}, "payload" : '{"text":"' + text + '"}', }; UrlFetchApp.fetch(webhookUrl, options); } 関数postSlackはbotが関数により生成されたテキストやデータを投稿するための関数です。 のちのちたくさん登場します。 内容としてはwebhookの設定+UrlFetchAppでslackにリクエストを飛ばしています。 webhookURLは個人個人で違うので、↑では伏字にしてます。 main.gs //Slackからの入力を受け取る function doPost(e) { if(e.parameter.user_name === "slackbot") return; const data = e.parameter.text; //入力した内容によってBotの返答を分岐させる。 switch (true) { //URLを含む場合、スプレッドシートに出力 case /https.*/.test(data): postSlack("入力を受け付けたよ!面白そうな本だね!"); //スプレッドシートに内容を出力 outputToSheet(data); break; //「list」と入力した場合、showListを呼び出し case /list/.test(data): showList(); break; //「recommend」と入力した場合、recommendBookを呼び出し case /recommend/.test(data): recommendBook(); break; //「help」と入力した場合、helpを呼び出し case /help/.test(data): postSlack("↓コマンドの一覧を表示↓"); help(); break; } } 関数doPostはslackに投稿された内容から各種処理を行うための関数です。 例えば、URLを投稿するとSpreadsheetにデータを入れるみたいな感じです。 caseを使い、入力内容によって呼び出す関数を変えます。 呼び出す関数は後ほど定義します。 main.gs //スプレッドシート用の変数定義 //現在のスプレッドシートを取得 const aBook = SpreadsheetApp.getActiveSpreadsheet(); //対象のシートを取得 const aSheet = aBook.getSheetByName("book_info_DB"); //A列の行数取得。 const rangeData = aSheet.getRange('B:B').getValues(); let lastRow = 0; for(let i=0; i<rangeData.length; i++){ if(rangeData[i][0]){ lastRow = i + 1; } } //本のタイトル,URL,ステータスの一覧を取得し配列に格納 const bookIdArray = aSheet.getRange(2,1,lastRow-1,1).getValues(); const bookUrlArray = aSheet.getRange(2,2,lastRow-1,1).getValues(); const bookTitleArray = aSheet.getRange(2,3,lastRow-1,1).getValues(); const bookStatusArray = aSheet.getRange(2,4,lastRow-1,1).getValues(); GASでデータのやりとりをするためにスプレッドシート用の変数を定義します。 またタイトル,URL,ステータスを配列に格納するための変数も定義します。 main.gs //スプレッドシートにslackでの投稿内容を出力するための関数 function outputToSheet(data) { //取得したデータを整形 const array = data.split(","); //取得したデータを整形するための正規表現を定義 const myRegeXp = "https.*[^>]"; //URLを整形 const url = array[0].match(myRegeXp); //ステータスを取得 const status = array[1]; //投稿日時を取得 const date = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd'); //整形した値と入力日時を最終行に出力 aSheet.getRange(lastRow+1,2).setValue(url); aSheet.getRange(lastRow+1,4).setValue(status); aSheet.getRange(lastRow+1,5).setValue(date); //もしステータスが未入力の場合は「未設定」を入力 if(aSheet.getRange(lastRow+1,4).getValue() === "") { aSheet.getRange(lastRow+1,4).setValue("未設定"); } } ↑はslack上にURLを投稿したときに発動する関数です。 冒頭で分岐させた関数の一つです。 スプレッドシートにURLとステータス、投稿日時を取得し入れます。 一応、「URL,status」の形で入力すれば、statusに購入済みや読破などのステータスを入れることができます。(statusの入力がない場合は未設定。) ただその後の変更をslack上で行う実装はしていないので、現状あまり使えません。。。 main.gs //登録している書籍一覧を「書籍名:ステータス」の形式でslack上に表示 function showList() { postSlack("リストを表示するね↓"); for(let i=0; i<bookIdArray.length; i++) { postSlack("ID:" + bookIdArray[i] + "\nTitle:" + bookTitleArray[i] + "\nURL:" + bookUrlArray[i] + "\nStatus:" + bookStatusArray[i]); } } ↑はslack上に「list」と投稿したときに発動する関数です。 こちらも冒頭で分岐させた関数の一つです。 配列に格納されたデータを元にスプレッドシート上にある書籍の一覧をpostSlack関数の引数として取り、slack上に表示します。 main.gs //一週間に一度、DBの中からランダムで一冊提案する function recommendBook() { //ランダムにおすすめする本のタイトル、URLを取得 const bookNum = Math.floor( Math.random() * bookIdArray.length); const recommendBookId = bookIdArray[bookNum]; const recommendBookTitle = bookTitleArray[bookNum]; const recommendBookUrl = bookUrlArray[bookNum]; //slackに出力用の文章 const recommendMessage = "こんにちは!\n今週はこの本をを読んでみるのはどう?\nID:" + recommendBookId + "\n" + "Title:" + recommendBookTitle + "\n↓にamazonのURL貼っとくね。\n" + recommendBookUrl; postSlack(recommendMessage); } ↑はslack上に「recommend」と投稿したときに発動する関数です。 こちらも冒頭で分岐させた関数の一つです。 配列に格納されたデータの中にあるうちランダムで一冊をpostSlack関数の引数として取り、slack上に表示します。 main.gs //HELP用のコマンドリスト表示 function help(){ //コマンドリストを配列に格納 const commandList = [ {command:'help', description:"Show a list of commands."}, {command:'list', description:"Show a list of books."}, {command:'recommend', description:"Recommend a book at random in the list."}, {command:'[url]', description:"Put information of books to SpreadSheet."} ] //投稿用にコマンドリストを整形 postSlack(commandList[0].command + "         " + commandList[0].description); postSlack(commandList[1].command + "           " + commandList[1].description); postSlack(commandList[2].command + "       " + commandList[2].description); postSlack(commandList[3].command + "          " + commandList[3].description); } ↑はslack上に「help」と投稿したときに発動する関数です。 こちらも冒頭で分岐させた関数の一つです。 配列commandListをpostSlackの引数として取り、コマンド一覧をslack上に表示します。 コマンドリストのdescriptionを揃えるために空白を入れているのですが、うまく揃ってないし、気持ち悪いので治したいです。。。 main.gs //一週間に一度、最近面白い本があったか聞く function doMessage(e) { const message = "こんにちは!\n最近何か読みたい本見つけた?"; postSlack(message); } ↑はbotが一週間に一度、面白い本があったか聞いてくるように設定した関数です。 内容はシンプルで、messageを関数postSlackの引数としてとり、slack上に表示させるというのを一週間に一度行うようにGAS側にトリガーを設定しています。 おわりに 今回のbotだと、一度入力した内容を修正するにはスプレッドシートを直接いじらないといけないのが自分的にすごくいけてないです。。。 slack上でbotとやりとりすることでスプレッドシートのデータを編集できるようにするのが次の目標です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

非同期通信を使って簡単な投稿機能のアプリを作ってみる(Rails6)

※この記事はrails初学者の方に向けて書いたものです。間違った解釈などあればご指摘いただければ幸いです。 非同期通信とは? 非同期通信とは、コンピュータ間で送信者のデータ送信タイミングと受信者のデータ受信タイミングを合わせずに通信を行う通信方式である。 つまり、送信者と受信者の両方がオンラインである必要がなく、片方が接続しているだけで通信が成立する。 非同期通信の対義語として、同期通信というものがある。 分かりやすく言うと、、、 相手に要求を出したら、(相手からの応答を待たないで)さっさと次の処理を進めることができる通信と表現できる。 非同期通信では、相手に要求(リクエスト)を投げたら、応答(レスポンス)が返ってくるまで待ちません。 通信というのは基本的にキャッチボールです。 相手に「要求」を投げて、「応答」を受け取ります。 要求を投げてから応答を受け取るまでには時間がかかります。要求の投げ方は2種類存在します。 1,応答が来るまで、何も処理を行わず待つ→同期通信 2,応答を待たずに、処理を進める→非同期通信 非同期通信を使用するメリット メリット 説明 操作性の向上 サーバーが処理を行っている間に操作できる パフォーマンスの向上 ページの一部のみ更新するため、通信量の削減が可能 機能を実現しやすくなる 編集中の文章を一時保存するなどの機能をユーザーに意識させることなく実装することができる バックエンドエンジニアを目指す際のポートフォリオに積極的に実装することで「サーバーへの負担」や「UXの向上」にまで気を遣っているのだというアピール材料になるそうなので今一度その概要をアウトプットしたいと思います。ここでは簡単な投稿アプリを作って動作確認をしながら非同期通信の基本的流れをおさらいしたと思います。 開発環境 ・Rails: 6.1.3.1 ・ruby: 2.7.2 ・jquery: 3.6.0 手順 1, アプリの準備 $ rails new ajax_sample $ cd ajax_sample $ rails g controller posts index $ rails g model Message title:string content:text $ rails db:migrate 2,bootstrapの導入 以下の記事を参考に導入しました。今回は本筋からそれてしまうので割愛します。 ・Rails 6にjQueryとBootstrapを入れる 3,postsコントローラの設定 posts_controller.rb class PostsController < ApplicationController def index @posts = Post.all @post = Post.new end def create Post.create(post_params) @posts = Post.all # redirect_toの代わりに入れる end private def post_params params.require(:post).permit(:title, :content) end end 4,ルーティングの設定 routes.rb Rails.application.routes.draw do root 'posts#index' resources :posts, only: [:index, :create] end 5,viewの作成 まずは投稿一覧画面に各記事を表示する部分テンプレートの_post.html.erbと投稿用のフォームとしてモーダルを配置します。 views/posts/index.html.erb <div class="container mt-5"> <div class="row"> <div class="col-3 sidebar"> <button type="button" class="btn btn-primary w-100" data-toggle="modal" data-target="#exampleModal"> 投稿 </button> </div> <div class="col-9 sidebar posts"> <%= render 'posts', posts: @posts %> </div> </div> </div> <!-- Modal --> <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalLabel">新規投稿</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">&times;</span> </button> </div> <%= form_for @post, remote: true do |f| %> <div class="modal-body"> <div class="form-group"> <%= f.label :title, "タイトル" %> <%= f.text_field :title, class:"form-control" %> </div> <div class="form-group"> <%= f.label :content, "本文" %> <%= f.text_area :content, class:"form-control" %> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button> <%= f.submit "投稿する", class:"btn btn-primary" %> </div> <% end %> </div> </div> </div> モーダルのデザインはbootstrapのチートシートから持ってきたものにアレンジを加えただけです。 ・Modal - Bootstrap 4.2 - 日本語リファレンス 次にindex.html.erbに配置した部分テンプレートを用意します。この中にはpostsテーブルの各要素を表示するためにeachメソッドを使用しています。 views/posts/_posts.html.erb <% posts.each do |post| %> <div class="card mb-1"> <div class="card-body"> <p class="card-text"><%= post.title %></p> <p><%= post.content %></p> </div> </div> <% end %> これで投稿一覧はひとまず表示することはできますが、このままでは非同期処理にはできません。同期処理であればpostsコントローラにredirect_toメソッドを記述して再度投稿一覧を描画し直せばいいのですが今回はページ遷移をなくしたいので追加でjavascriptのファイルが必要です。 views/posts/create.js.erb $("textarea").val(""); $("input").val(""); $('#exampleModal').modal('hide') $('.posts').html("<%= escape_javascript(render 'posts', posts: @posts) %>") 上記のファイルでは投稿フォームのリセット、モーダルウィンドウの終了、新しい投稿一覧の書き換えを行っています。 終わりに 非同期通信は実装できれば画面遷移でユーザーにストレスを与えることもサーバーに必要以上に負荷をかけることなくサービスを運用できるため重宝されているそうです。まだまだ私も理解が追いついていない部分も多いので今後も積極的に実装していこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

消費税計算③

概要 ①inputタブのid属性に入力した値をインプット ②JS側で計算(加工)今回は消費税の計算になります。 ③テーブルタグに出力。 ①HTML インプット index.html <form> <label>式:</label> <input type="tel" placeholder="価格を入力してください" id="number"> <input class="btn" type="button" value="計算"id="count"> </form> テキスト、ボタンそれぞれにid属性を付けます。 ②JS 加工、アウトプット index.js const reckoning = document.querySelector('[type="button"]'); reckoning.addEventListener('click',e =>{ function totalTax() { const taxNot = document.getElementById('number').value; console.log(taxNot); const total = Math.round(taxNot * (1+0.1)); return total; } const sub = totalTax(); document.getElementById('total').textContent = sub; }); ①ボタンを押す(イベント)発生 ②テキストから値を取得(valueプロパティ ▶ フォームのインプットやtextareaの値や文字列を取得し、要素の中身を操作します。) ③Math.roundで小数点以下を四捨五入します。 ④定数subに関数を呼び出す。 ⑤テキストのid取得し、subを代入。 ⑥テーブルに出力
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゲームパッド・イベント変換

Gamepad.html <!DOCTYPE html> <html lang="ja"><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script> var Minecraft = { keys:{ X:'LeftButton', Y:'RightButton', A:'Space', B:'E', LY_NEGATIVE:'W', LX_NEGATIVE:'A', LY_POSITIVE:'S', LX_POSITIVE:'D', UP:'F5', DOWN:'Q', LEFT:'F', SELECT:'F11', START:'Escape', LB:e=>{return Minecraft.hotbar(e,-1)}, RB:e=>{return Minecraft.hotbar(e,1)}, LT:'LControlKey', RT:'LShiftKey', LS:'F3', RS:'F11', }, mousemove:'RightStick', hotbarId:0, hotbar(e,d){ if(e.type=='down'){ Minecraft.hotbarId += 9+d Minecraft.hotbarId %= 9 } e.type = 'key'+e.type e.key = 'D'+(Minecraft.hotbarId+1) return e } } $(e=>{ var n = new Controller.Native() var v = new Controller.View() var s = new Controller.Status().on(e=>{ var event = new Controller.Event(e,Minecraft) n.dispatch(event) if(event.type) v.log(JSON.stringify(event).replace(/"/g,'')) }) setInterval(e=>{ s.loop() v.fps().sticks(s.gamepadAxes) },1000/60) }) var Controller = {} Controller.Native = class{ constructor(){ if(chrome.webview){ this.bridge = chrome.webview.hostObjects.bridge this.host = chrome.webview.hostObjects }else{ this.bridge = {dispatch:console.log} this.host = {bridge:this.bridge} } } dispatch(e){ if(e.type){ var s = JSON.stringify(e) this.host.bridge.dispatch(s) } } } Object.assign((Controller.Data = class{ constructor(){ this.count = 0 this.connection = {} this.listener = console.log this.dummy = { buttons:this.buttons.map(k=>{return {pressed:false}}), axes:this.axes.map(k=>{return 0}) } } button(a,b,i){ if(a.pressed==b.pressed) return null switch(b.pressed){ case(true): this.listener({type:'down', key:this.buttons[i]});break default: this.listener({type:'up', key:this.buttons[i]}) } } stick(a,b,i){ if(a==b) return null var p = this.axes[i]+'_POSITIVE' var n = this.axes[i]+'_NEGATIVE' switch(true){ case(a>0): this.listener({type:'up', key:p});break case(a<0): this.listener({type:'up', key:n});break } switch(true){ case(b>0): this.listener({type:'down', key:p});break case(b<0): this.listener({type:'down', key:n});break } } move(axes,s){ if(axes.filter(v=>Math.abs(v)>0.15).length==0) return null this.listener({type:'move',axes:axes,stick:s}) } }).prototype,{ buttons:'A,B,X,Y,LB,RB,LT,RT,SELECT,START,LS,RS,UP,DOWN,LEFT,RIGHT,B16'.split(','), axes:'LX,LY,RX,RY'.split(',') }) Controller.Status = class extends Controller.Data { constructor(){ super() window.addEventListener("gamepadconnected",e=>{ console.log(e) this.count++ this.status(e.gamepad) }); window.addEventListener("gamepaddisconnected",e=>{ console.log(e) this.connection[e.gamepad.index] = null this.count-- }); this.gamepadAxes = [] } on(f){ this.listener = f return this } loop(){ if(this.count==0) return this.item = Array.prototype.slice.call(navigator.getGamepads()) this.item.forEach(g=>this.status(g)) return this } status(gamepad){ if(gamepad == null) return var g = this.connection[gamepad.index]||this.dummy gamepad.buttons.slice(0,16).forEach((v,i)=>this.button(g.buttons[i],v,i)) gamepad.axes.forEach((v,i)=>this.stick(Math.round(g.axes[i]),Math.round(v),i)) this.move(gamepad.axes.slice(0,2),'LeftStick') this.move(gamepad.axes.slice(2,4),'RightStick') this.connection[gamepad.index] = gamepad this.gamepadAxes = gamepad.axes } } Object.assign((Controller.Event = class{ constructor(e,data){ var v = data.keys[e.key]||null if(e.type=='move'){ if(e.stick==data.mousemove) this.mouseMove(e) }else if(v){ if(typeof v==='string'){ var k = data.keys[e.key] var i = this.MOUSE_BUTTONS.indexOf(k) if(i==-1){ this.type = 'key'+e.type this.key = k }else{ this.type = 'mouse'+e.type this.button = i } }else{ Object.assign(this,v(e)) } } } mouseMove(e){ this.type = 'mousemove' this.x = this.mouseSpeed(e.axes[0]) this.y = this.mouseSpeed(e.axes[1]) } mouseSpeed(v){ var a = Math.abs(v) if(a==1) return v*18 return v*6 } }).prototype,{ type:null, MOUSE_BUTTONS:'LeftButton,MiddleButton,RightButton'.split(',') }) Controller.View = class{ constructor(){ this.time = performance.now() this.$view=$('<div id="View">').append( this.$log=$('<div id="Log">'), this.$axe=$('<div id="Axis">').append( $('<div class="range">').append(this.$ls=$('<div class="stick">')), $('<div class="range">').append(this.$rs=$('<div class="stick">')) ), this.$fps=$('<div id="Fps">') ) $('body').append(this.$view) } log(v){ this.$log.append($('<div>').append(v)) this.$log.children().slice(0,-100).remove() window.scroll(0,$(document).height()) return this } sticks(a){ if(a==null) return if(a.length<4) return this.$ls.css({left:a[0]*30,top:a[1]*30}) this.$rs.css({left:a[2]*30,top:a[3]*30}) return this } fps(){ var t = performance.now() this.$fps.empty().append(Math.round(1000/(t-this.time)),'FPS') this.time = t return this } } </script> <style> *{ box-sizing:border-box; margin:0;padding:0; } body{ font-size:12px; line-height:20px; } #Log div{ border-bottom:1px solid rgba(0,0,0,0.04); padding:0 10px; white-space:nowrap; text-overflow:ellipsis; overflow:hidden; } #Axis{ position:fixed; right:0;top:0; width:280px;height:160px; } #Axis .range{ position:absolute; top:30px; width:100px; height:100px; border-radius:100px; background-color:rgba(0,0,0,0.1); } #Axis .range:nth-child(1){left:30px;} #Axis .range:nth-child(2){right:30px;} #Axis .stick{ position:absolute; transform: translate(25px,25px); width:50px; height:50px; border-radius:30px; border:1px solid rgba(0,0,0,0.1); background-color: white; } #Fps{ position: fixed; right:0;top:0; padding:10px; } </style> </head></html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゲームパッドの入力と変換

Gamepad.html <!DOCTYPE html> <html lang="ja"><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script> var Minecraft = { keys:{ X:'LeftButton', Y:'RightButton', A:'Space', B:'E', LY_NEGATIVE:'W', LX_NEGATIVE:'A', LY_POSITIVE:'S', LX_POSITIVE:'D', UP:'F5', DOWN:'Q', LEFT:'F', SELECT:'F11', START:'Escape', LB:e=>{return Minecraft.hotbar(e,-1)}, RB:e=>{return Minecraft.hotbar(e,1)}, LT:'LControlKey', RT:'LShiftKey', LS:'F3', RS:'F11', }, mousemove:'RightStick', hotbarId:0, hotbar(e,d){ if(e.type=='down'){ Minecraft.hotbarId += 9+d Minecraft.hotbarId %= 9 } e.type = 'key'+e.type e.key = 'D'+(Minecraft.hotbarId+1) return e } } $(e=>{ var n = new Controller.Native() var v = new Controller.View() var s = new Controller.Status().on(e=>{ var event = new Controller.Event(e,Minecraft) n.dispatch(event) if(event.type) v.log(JSON.stringify(event).replace(/"/g,'')) }) setInterval(e=>{ s.loop() v.fps().sticks(s.gamepadAxes) },1000/60) }) var Controller = {} Controller.Native = class{ constructor(){ if(chrome.webview){ this.bridge = chrome.webview.hostObjects.bridge this.host = chrome.webview.hostObjects }else{ this.bridge = {dispatch:console.log} this.host = {bridge:this.bridge} } } dispatch(e){ if(e.type){ var s = JSON.stringify(e) this.host.bridge.dispatch(s) } } } Object.assign((Controller.Data = class{ constructor(){ this.count = 0 this.connection = {} this.listener = console.log this.dummy = { buttons:this.buttons.map(k=>{return {pressed:false}}), axes:this.axes.map(k=>{return 0}) } } button(a,b,i){ if(a.pressed==b.pressed) return null switch(b.pressed){ case(true): this.listener({type:'down', key:this.buttons[i]});break default: this.listener({type:'up', key:this.buttons[i]}) } } stick(a,b,i){ if(a==b) return null var p = this.axes[i]+'_POSITIVE' var n = this.axes[i]+'_NEGATIVE' switch(true){ case(a>0): this.listener({type:'up', key:p});break case(a<0): this.listener({type:'up', key:n});break } switch(true){ case(b>0): this.listener({type:'down', key:p});break case(b<0): this.listener({type:'down', key:n});break } } move(axes,s){ if(axes.filter(v=>Math.abs(v)>0.15).length==0) return null this.listener({type:'move',axes:axes,stick:s}) } }).prototype,{ buttons:'A,B,X,Y,LB,RB,LT,RT,SELECT,START,LS,RS,UP,DOWN,LEFT,RIGHT,B16'.split(','), axes:'LX,LY,RX,RY'.split(',') }) Controller.Status = class extends Controller.Data { constructor(){ super() window.addEventListener("gamepadconnected",e=>{ console.log(e) this.count++ this.status(e.gamepad) }); window.addEventListener("gamepaddisconnected",e=>{ console.log(e) this.connection[e.gamepad.index] = null this.count-- }); this.gamepadAxes = [] } on(f){ this.listener = f return this } loop(){ if(this.count==0) return this.item = Array.prototype.slice.call(navigator.getGamepads()) this.item.forEach(g=>this.status(g)) return this } status(gamepad){ if(gamepad == null) return var g = this.connection[gamepad.index]||this.dummy gamepad.buttons.slice(0,16).forEach((v,i)=>this.button(g.buttons[i],v,i)) gamepad.axes.forEach((v,i)=>this.stick(Math.round(g.axes[i]),Math.round(v),i)) this.move(gamepad.axes.slice(0,2),'LeftStick') this.move(gamepad.axes.slice(2,4),'RightStick') this.connection[gamepad.index] = gamepad this.gamepadAxes = gamepad.axes } } Object.assign((Controller.Event = class{ constructor(e,data){ var v = data.keys[e.key]||null if(e.type=='move'){ if(e.stick==data.mousemove) this.mouseMove(e) }else if(v){ if(typeof v==='string'){ var k = data.keys[e.key] var i = this.MOUSE_BUTTONS.indexOf(k) if(i==-1){ this.type = 'key'+e.type this.key = k }else{ this.type = 'mouse'+e.type this.button = i } }else{ Object.assign(this,v(e)) } } } mouseMove(e){ this.type = 'mousemove' this.x = this.mouseSpeed(e.axes[0]) this.y = this.mouseSpeed(e.axes[1]) } mouseSpeed(v){ var a = Math.abs(v) if(a==1) return v*18 return v*6 } }).prototype,{ type:null, MOUSE_BUTTONS:'LeftButton,MiddleButton,RightButton'.split(',') }) Controller.View = class{ constructor(){ this.time = performance.now() this.$view=$('<div id="View">').append( this.$log=$('<div id="Log">'), this.$axe=$('<div id="Axis">').append( $('<div class="range">').append(this.$ls=$('<div class="stick">')), $('<div class="range">').append(this.$rs=$('<div class="stick">')) ), this.$fps=$('<div id="Fps">') ) $('body').append(this.$view) } log(v){ this.$log.append($('<div>').append(v)) this.$log.children().slice(0,-100).remove() window.scroll(0,$(document).height()) return this } sticks(a){ if(a==null) return if(a.length<4) return this.$ls.css({left:a[0]*30,top:a[1]*30}) this.$rs.css({left:a[2]*30,top:a[3]*30}) return this } fps(){ var t = performance.now() this.$fps.empty().append(Math.round(1000/(t-this.time)),'FPS') this.time = t return this } } </script> <style> *{ box-sizing:border-box; margin:0;padding:0; } body{ font-size:12px; line-height:20px; } #Log div{ border-bottom:1px solid rgba(0,0,0,0.04); padding:0 10px; white-space:nowrap; text-overflow:ellipsis; overflow:hidden; } #Axis{ position:fixed; right:0;top:0; width:280px;height:160px; } #Axis .range{ position:absolute; top:30px; width:100px; height:100px; border-radius:100px; background-color:rgba(0,0,0,0.1); } #Axis .range:nth-child(1){left:30px;} #Axis .range:nth-child(2){right:30px;} #Axis .stick{ position:absolute; transform: translate(25px,25px); width:50px; height:50px; border-radius:30px; border:1px solid rgba(0,0,0,0.1); background-color: white; } #Fps{ position: fixed; right:0;top:0; padding:10px; } </style> </head></html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】LevelLabelをfor文で

前置き FizzBuzz問題の次を考えた!その名も「LevelLabel問題」 - Qiita https://qiita.com/yamitake@github/items/6a82de2cf235544f2f97 上記記事で提案された新たな問いです。 コメントで良さげな解があるのでいまさらですが。。。 私も微妙なコメントをした記事ですが、改めて考えてみました。 LevelLabelとは 3つ以上の連番をまとめて文字列で出力。 元記事では「・」と「~」で区切ってますが、この記事では「,」と「-」で区切ります。 //こんな配列を [1,2,3,5,7,9,10,11,20,21] //こうまとめる '1-3,5,7,9-11,20,21' コード const levelLabel = a => { const r = []; for(let i=0,s=0; i<a.length; s=i){ //要素の前後を比較して条件が合えば`変数i`を増やして処理対象をワープ while (a[i]===a[++i]-1) {} r.push(i-s<3 ? a.slice(s,i) : a[s]+'-'+a[i-1]); } return r.join(); }; const arr = [1,2,3,5,7,9,10,11,20,21]; console.log( levelLabel(arr) ); //1-3,5,7,9-11,20,21 コードゴルフには弱い
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

消費税計算②

クリックしてから、アラートとして表示するまでのプログラムです。 index.html <form> <label>式:</label> <input type="tel" placeholder="2000" > <input class="btn" type="button" value="計算" id="count"> </form> 今回はHTMLのformから入力して、計算ボタンを押したときに値をアラートで返せるように実装していきます。 index.js // クリックイベントで消費税関数処理終了からのアラートで計算結果表示 const reckoning = document.querySelector('[type="button"]'); reckoning.addEventListener('click',e =>{ const tax = 0.1; function totalTax(taxNot) { const total = taxNot + taxNot * tax; return total; } const sub = totalTax(3000); window.alert(sub); }) ①定数 reckoning(計算)にquerySelectorボタンを代入します。 ②先程代入した、reckoningにイベントを設定します。 ③消費税の関数 ④定数subに関数totalTaxを呼び出し、3000を代入します。 ⑤それをwindow.alertで呼び出します。 関数スコープ 今回、クリックイベントの中で使われている税込み計算の関数は、クリックイベントの中でのみ有効となります。 クリックイベントの外で呼び出してもエラーになります。 次回 ユーザーが税別の金額を入力したら税込み計算を返せるようにプログラムを組む。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで巨大な配列処理を行う時はジェネレーターが便利

JavaScriptで配列から指定した数の組合せを作る関数を作ったところ、組合せの数が数千万になるケースがあり、Node.js(v14.16.0)で実行したら使用できるメモリの上限に達したというエラーが出てしまいました。 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory 以下のように--max-old-space-sizeオプションを指定してNode.jsで使用できるメモリを増やせば動くようになったのですが、 $ node --max-old-space-size=8192 index.js ES2015で追加されたジェネレーター(Generator)を教えてもらったので書き直してみました。 差分 - function combine(list, n) { + function* combine(list, n) { const len = list.length; - const result = []; const indexes = []; for (let i = 0; i < n; i++) { indexes.push(i); } const lastPos = n - 1; while (indexes[0] + n <= len) { while (indexes[lastPos] < len) { - result.push(indexes.map(i => list[i])); + yield indexes.map(i => list[i]); indexes[lastPos]++; } for (let pos = lastPos - 1; pos >= 0; pos--) { indexes[pos]++; const lastIdx = indexes[pos] + (lastPos - pos); if (lastIdx < len) { for (; pos + 1 < n; pos++) { indexes[pos + 1] = indexes[pos] + 1; } break; } } } - - return result; } const list = ['a', 'b', 'c', 'd', 'e']; const n = 3; - const result = combine(list, n); - console.log(result); + for (const combi of combine(list, n)) { + console.log(combi); } /* - [ - [ 'a', 'b', 'c' ], - [ 'a', 'b', 'd' ], - [ 'a', 'b', 'e' ], - [ 'a', 'c', 'd' ], - [ 'a', 'c', 'e' ], - [ 'a', 'd', 'e' ], - [ 'b', 'c', 'd' ], - [ 'b', 'c', 'e' ], - [ 'b', 'd', 'e' ], - [ 'c', 'd', 'e' ] - ] + [ 'a', 'b', 'c' ] + [ 'a', 'b', 'd' ] + [ 'a', 'b', 'e' ] + [ 'a', 'c', 'd' ] + [ 'a', 'c', 'e' ] + [ 'a', 'd', 'e' ] + [ 'b', 'c', 'd' ] + [ 'b', 'c', 'e' ] + [ 'b', 'd', 'e' ] + [ 'c', 'd', 'e' ] */ 修正前のコードは、組合せの配列を作り、その配列を返していたのですが、修正した方はループしつつも値を一つずつ返すようになります。 何を言っているのかわからないと思うのですが、ジェネレーターはyieldで値を返し、そこで処理を一時停止します。 そして次呼ばれた時に続きから実行し、またyieldに到達したら値を返して一時停止します。 一時停止するってすごいですよね。 値を順番に一つずつ返す考え方はイテレーターと同じで、ジェネレーターはイテレーターを書きやすくしたものだそうです。 以下のようなジェネレーターの使用例を見てもメリットがよくわからなかったのですが、こういう風に使うとメモリへの負担を少なくできるということがわかりました function* generator() { yield 1; yield 2; yield 3; } const gen = generator(); // "Generator { }" for (const v of gen) { console.log(v); } /* 1 2 3 */ function* infinite() { let index = 0; while (true) { yield index++; } } const generator = infinite(); // "Generator { }" console.log(generator.next().value); // 0 console.log(generator.next().value); // 1 console.log(generator.next().value); // 2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

明日の予定をGASで毎日自分のメアドに送信させる。

え、この予定明日じゃん。ってことは今は... 「明後日の予定を明日の予定と間違える」というなんともあほらしいことを二回もやってしまった。Googleカレンダーのアプリを週で表示していて、1列違うところを見てしまっていた。 ならばということで、テキスト情報で自分のメールに飛ばすように設定した。だいたい夜の21時くらいに送ってもらえば寝る前かつ明日の準備もできるかつ覚えてられるかなということで、21時から22時の間に毎日定期実行されるようにした。 注意!以下のコードは最後のhoge@example.comを自分の受信したいメアドに変更してください これはコピペで使われるのが一番多いと思うので、一応注意書き。 const main = () => { const tomorrow = new Date() tomorrow.setDate(tomorrow.getDate() + 1) const tomorrowEventsOnDefaultCal = CalendarApp.getDefaultCalendar().getEventsForDay(tomorrow) const msg = tomorrowEventsOnDefaultCal.reduce((previousMsg, event) => { const start = event.getStartTime() const startHours = start.getHours() > 9 ? start.getHours() : '0' + start.getHours() const startMinutes = start.getMinutes() > 9 ? start.getMinutes() : '0' + start.getMinutes() const end = event.getEndTime() const endHours = end.getHours() > 9 ? end.getHours() : '0' + end.getHours() const endMinutes = end.getMinutes() > 9 ? end.getMinutes() : '0' + end.getMinutes() const result = `${startHours}:${startMinutes} - ${endHours}:${endMinutes} ${event.getTitle()}\n` return previousMsg + result }, ''); GmailApp.sendEmail('hoge@example.com', '明日の予定', msg) } 軽く機能紹介 時間を簡単にフォーマットした。 関数化する方がよいですね。 またメンテするときに変更して記事も変えます。メンテしなかったらなんもせんです。 Array.prototype.reduceを使ってスマートに見せてる。 ひっさびさにjs書いたので、もっとスマートなのがあった気がしてならない。 最初はforeachで書いて、あれ?と思って調べたらreduceが出てきた。 定期実行は何時にしてもたぶん明日の予定を表示してくれる。自分の場合は21時から22時にしたけど、別に何時でもよい。 最後に GASってコピペで動いてほしいですよね。 もしコピペ(+メアド編集)で動かなかったら教えてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + Material-UI: 無効化(disabled)したButtonにTooltipを表示・非表示する

Material-UIのTooltipコンポーネントをButtonに使ったとき、disabledとの兼ね合わせで少し戸惑ったことについて書き留めます。 Material-UIとTooltipコンポーネント Material-UIは、Googleが提唱するMaterial Designに則った、ユーザーインタフェースを提供するReactコンポーネントライブラリです(Material Designについては、「Googleが推奨する『マテリアルデザイン』とは?5つの特徴と、メリット・デメリット・作り方について解説」参照)。豊富なコンポーネントとシンプルで直感的なデザイン・操作感に定評があります。 Tooltipコンポーネントの使い方は、マウスが重なったたとき表示したい部品を包むだけです。titleに与えたテキストがツールチップとして表れます。 <Tooltip title="tooltip" arrow> <Button variant="contained" color="primary"> button </Button> </Tooltip> 無効化(disabled)したButtonにTooltipを出す 問題は、無効化(disabled)したButtonをTooltipコンポーネントで包んだ場合です。以下のようなメッセージが示されてしまいます。無効化されたButtonからはユーザー操作のイベントが送られないので、Tooltipが働かないということです。 <Tooltip title="disabled" arrow> // 変数disabledの値はtrue <Button variant="contained" color="primary" disabled={disabled}> disabled button </Button> </Tooltip> Material-UI: You are providing a disabled button child to the Tooltip component. A disabled element does not fire events. Tooltip needs to listen to the child element's events to display the title. でも、この問題については公式サイトの「無効な要素」(原文「Disabled Elements」)できちんと説明されています。Buttonを別の要素、たとえば<span>でつぎのようにくるんでしまえばいいのです。 <Tooltip title="disabled" arrow> <span> <Button variant="contained" color="primary" disabled={disabled}> disabled button </Button> </span> </Tooltip> 実は、メッセージにもそのヒントが示されていました。 Add a simple wrapper element, such as a span. 動的に変わるButtonの有効・無効に応じてTooltipの表示・非表示を切り替える 筆者が困ったのは、無効化したButtonにはTooltipを出したくないという場合でした。それなら、Tooltipを外せばいいと思われるかもしれません。でも、やりたかったのは、有効・無効は動的に切り替えてTooltipを表示・非表示したいということです。 そこで、無効のときは包んだ要素のstyleに、pointer-events: "none"を加えることで対応しました。以下にCodePenのサンプルも添えます。Buttonの有効・無効は、Checkboxで切り替えてください。 <Tooltip title="enabled" arrow> <span style={disabled ? { pointerEvents: "none" } : {}}> <Button variant="contained" color="primary" disabled={disabled}> button </Button> </span> </Tooltip> See the Pen React + Material-UI: Tooltip issue by Fumio Nonaka (@FumioNonaka) on CodePen.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 勉強日記 1日目(4/23)

内容 Railsの学習の予定を立てた。 メモ Rails(Ruby on Rails)を段階的に学んでいくための予定を以下でまとめる。 目標 簡単なWebアプリケーションを作れるレベルになる 自分のブログを構築する 現状 HTMLやCSSを読むことはできるが、実際に書いたことはほとんどない Rubyも雰囲気はわかるが、実際に書いたことはほとんどない JavaScriptは全くわからない Railsはチュートリアルの初めの方だけやったことがある 段階 HTML、CSS、Ruby、JavaScript、Railsに慣れる Progateを数回やる 段階的にそれぞれの技術のレベルを上げる Rails Girlsを進める Railsチュートリアルを進める 目標となる物を作る (目標の通り)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Web Bluetooth API で toio を 6台同時に制御する

toio の BLE経由での制御について、Node.js + noble を使ったプログラムを試しに作って、以下の記事を書きました。以下の記事の中でも、この記事のタイトルにある 6台同時制御をやっていたりします。 ●あらためて noble でガジェットを扱う話に着手する: toio と micro:bit を複数混在させてスキャン、toio の複数制御(6台分) - Qiita  https://qiita.com/youtoy/items/15a743d692f348327ff9 そして、上記の記事のプログラムを書いている時、ふと「以前、何度も使っていた Web Bluetooth API で toio を同時制御していた時、そのソースを改変して同時制御の台数を増やしても 3〜4台までしか扱えない状態だったけれど、あれは実装の問題では?」という考えが頭をよぎりました。 そして、以前から実装していた Web Bluetooth API を使った toio の複数台の処理のプログラムを書き直して、6台同時に動かせるかを試しました。結論から言うと、以下のように 6台同時制御に成功しました。 自宅にある #toio 6台の同時制御シリーズ、第2弾。「6台同時接続&全てに同じモーター制御を適用」という実装を Web Bluetooth API を使って書きました(実行はブラウザ上)。以前、何度も書いていたプログラムの実装を見直してたらできてしまった(今までの実装だと、同時に 3〜4台までだった)。 pic.twitter.com/NUFRVPEqhV— you (@youtoy) April 25, 2021 ちなみに、台数が 6台になってるのは、単純に自分が持っている台数が 6台だから、という理由です。 実装内容 ソースコード全体は、以下になります。 <!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> <button class="button is-info is-light" type="button" onclick="onButton02Click()">一斉動作</button> </div> </div> </section> <script> const TOIO_SERVICE_UUID = "10b20100-5b3b-4571-9508-cf3efcd7bbae"; const MOTOR_CHARACTERISTIC_UUID = "10b20102-5b3b-4571-9508-cf3efcd7bbae"; const motorBuf = new Uint8Array([0x02, 0x01, 0x01, 0x32, 0x02, 0x02, 0x32, 0x78,]); let myCharacteristics = []; async function onStartButtonClick() { let serviceUuid = TOIO_SERVICE_UUID; try { console.log("Requesting Bluetooth Device..."); const device = await navigator.bluetooth.requestDevice({ filters: [{ services: [serviceUuid] }], }); console.log("Connecting to GATT Server..."); const server = await device.gatt.connect(); console.log("Getting Service..."); const service = await server.getPrimaryService(serviceUuid); console.log("Getting Characteristic..."); const characteristic = await service.getCharacteristic( MOTOR_CHARACTERISTIC_UUID ); myCharacteristics.push(characteristic); console.log(myCharacteristics.length); } catch (error) { console.log("Argh! " + error); } } async function onButton02Click() { if (myCharacteristics.length !== 0) { for (const element of myCharacteristics) { console.log("write"); await element.writeValue(motorBuf); } } } </script> </body> </html> HTML の部分はボタンを 2つ作っていて、接続処理用のものとモーター制御用のものです。 接続用ボタンは使い回しをする形で、1個を繰り返し使うことで 6台分の接続処理を行えます(1台ずつ手動でつないでいく流れ)。もう 1つのモーター制御用のボタンは、ボタンが押された時点でつながっている全ての toio のモーター制御を行います。 モーター制御用のバイナリデータは、toio の通信仕様のモーターの部分にある「時間指定付きモーター制御」を使っています。また、toio関連の UUID 2つも、以下の仕様に掲載されているものです。  ●モーター · toio™コア キューブ 技術仕様   https://toio.github.io/toio-spec/docs/assets/motor_cube_direction.svg その他の部分は、過去に書いた記事でも使ったノウハウを利用しています。  ●toio を Web Bluetooth API で制御(「通知・読み出し・書き込み」を行う) - Qiita   https://qiita.com/youtoy/items/791905964d871ac987d6  ●toio を音で制御してみた(Audio用の Teachable Machine でベルやタンバリンの音を機械学習) - Qiita   https://qiita.com/youtoy/items/37f70bb4ce630e6cbd92 まとめ Web Bluetooth API でも 6台同時に制御するというのができたので、Node.js実装のものとは違った活用法を見つけられればと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

consoleで見るとプロパティが明らかに存在するのにundefinedが返される場合[chrome]

なんで明らかに存在するって断言できるの? sample.js console.log(userData.fav); //{word:[...]} console.log(userData.fav.word); //undefined あれ...存在しているはずじゃん?? 原因 Chromeのconsoleはどうやらconsoleの▶を開いて詳細を確認しようとした時点での状態を表示するようです。つまり実際にはuserData.favは{}であるのにも関わらず、その0.00001s後にwordプロパティが追加された場合consoleを確認するときにはすでにプロパティが存在しています。 つまり console.log(userData.fav);を「実行した」とき、プロパティは存在しない。 ↓ console.log(userData.fav.word);を「実行した」とき、プロパティは存在しないのでundefinedを返す ↓ 開発者がconsoleを開いてconsole.log(userData.fav);の表示結果を「確認する」とき、すでにプロパティwordが追加されたのでconsoleに表示される。 なのでPCがプロパティを追加するのよりも先にconsole画面で表示させれば{}と表示されるはずです。 ちなみに▶をクリックして展開する必要のないundefinedのような文字列は一度表示されたら変更されることはありませんが▶{__ ob __: Observer}のように展開する必要があるものは中身を確認した時点の状態を返すようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

const、let等の変数宣言

JavaScriptの変数について そもそも変数とは 変数とは値に名前をつける。 例えば、犬を飼っているとして、柴犬にポチと名前をつけるのと一緒です。 好きな名前をつけれるし、呼びやすいし愛着湧くでしょう。 型の種類、var let constについて JavaScriptでは三つの型があります。 var ・varは上書き可能 ・varは再宣言可能 var hoge01 = "hoge01変数"; console.log(hoge01); } consoleではこのように表示されます。 hoge01変数 var hoge01 = "hoge01変数"; hoge01 = "hoge01変数を上書き"; console.log(hoge01); } consoleでは上書きされてこのように表示されます。 hoge01変数上書き このようにvarで宣言すると、プロジェクトが大きくなると意図しないところで変数を上書きしてしまい 不具合の原因になります。 let ・letは上書き可能 ・letは再宣言不可能 let hoge02 = "hoge02変数"; hoge02 = "hoge02変数を上書き"; console.log(hoge02); こちらもvar同様に上書きされコンソールにはこのように表示されます。 hoge02変数を上書き let hoge02 = "hoge02変数を再宣言"; let変数を再宣言しようとすると、コンソールにこのようなエラーが出ます Uncaught SyntaxError: Identifier 'hoge02' has already been declared もうすでにhoge02は宣言されているとエラーが出ます。 letだと再宣言されるとエラーが出るのでバグを未然に防げます const ・constは上書き不可 ・constは再宣言不可 const hoge03 = "hoge03変数"; hoge03 = "hoge変数を上書き"; console.log(hoge03); const変数を上書きしようとするとコンソールにエラーが出ます。 Uncaught TypeError: Assignment to constant variable. const hoge03 = "hoge03変数"; const hoge03 = "hoge03を再宣言"; console.log(hoge03); const変数を再宣言しようとするとlet同様にコンソールにエラーが出ます Uncaught SyntaxError: Identifier 'hoge03' has already been declared 変数宣言について 型 変数名 = 値 まとめ varは再宣言、上書きなんでもできる。 便利だがプロジェクトが大きくなると不具合の原因になる可能性がある。 letは再宣言はできないが上書きが可能、なのでfor分の変数とかによく使われる。 constは上書き、再宣言共に不可能だが、constで定義した、配列、オブジェクトのプロパティは変更可能 なのでほとんどの宣言がconstでいいのかな。 最後まで読んでいただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む