- 投稿日:2020-01-16T23:57:27+09:00
あれやこれやしてても、たった3時間ぐらいで簡単なWEBアプリを作ってUnunoで公開した話
簡単なwebアプリを1から作って、3時間ぐらいで完全無料ホスティングUnuboで公開までたどりついた話をします。
お名前で取得した独自ドメインを設定して、HTML/CSS/JavaScriptを使ったスタティックな簡単なwebアプリを公開しました。
Unuboとは
■ Unubo - Cloud deployments made easy
Unuboとは、Webアプリ、DB、ブログを完全無料でホスティングできるサービスです。完全無料のホスティングを探していて、ここあたりの情報がでてきました。
■ Node.js,Python,RubyなどのWebアプリを完全無料でホスティング可能な「Unubo」を使ってみた!
■ Unuboを使ってみた話Unuboっていうのがあるんだ〜と知ったのが、この記事を書き始めてから、昼飯挟んで約5時間ぐらい前のことです(笑)
実際に使った感想は、めちゃくちゃ簡単!しかも無料!です。
やったこと
やったことは、webアプリ作成、Unuboでの公開設定と独自ドメイン設定です。
- webアプリ作成とUnuboでの公開設定
- Unuboにサインイン
- webアプリをGitHubにPUSH
- GitHubとUnuboを連携
- デプロイ、そして公開!
Unuboは、webアプリをGithubにPUSHすると自動的にデプロイしてくれます!
- 独自ドメイン設定
- お名前でドメイン取得
- お名前でDNS設定
- Unuboにドメインを設定
webアプリをGitHubにPUSH
GitHubの使い方は、ご存知かと思いますが、以下のサイトがわかりやすかったです。
独自ドメイン設定方法
自身のアプリ管理画面のDomainsというところに独自ドメイン設定方法が書かれています。ドキュメント見てもここに書いてあるって書いてました。
独自ドメインを取得した会社のDNSレコードに、Unuboの指定するアドレス登録した後、Unuboに独自ドメインを追加します。
まずは、お名前の管理画面から取得した独自ドメインのDNS設定をしました。
- DNS設定/転送設定を開きます
- DNSレコード設定を利用するの設定するボタンを押します。
$ dig asia.unubo.app
asia.unubo.app. 34.87.61.225CNAMEを使う場合は、Aレコードを追加しないといけないとのことなので、digで「asia.unubo.app」のIPアドレスを取得してAレコードに設定しました。AレコードとCNAMEレコードを次のように設定しました。
ホスト名 TYPE TTL VALUE tateyomi.xyz A 3600 34.87.61.225 www.tateyomi.xyz cname 3600 asia.unubo.app DNSレコード設定用ネームサーバー変更確認にチェック入れて、確認画面へ進んで、設定完了。
※既存ドメインでは、設定によって既存サイトがみれなくなってしまったりもあるので、自己責任のもと、きちんと確認して設定ください。
独自ドメインがインターネットに反映されるまで待ちます。
反映されたであろう、しばらくしてから、UnuboのDomainsの②から無料SSLと一緒に独自ドメインを追加します。
独自ドメインのDNS設定後、すぐにこれを設定するも、反映されていなかったのか一向に待っても無理でした。
独自ドメインのDNS設定したら、しばらく時間をあけて、この設定をしましょう!
設定終わってほっておくと、いとも簡単にUnubo上で独自ドメインでサイト開設!となりました。Unubo、ほんとちょっとしたwebアプリをすぐ公開したいときには、ほんと便利ですね!
ちなみに公開したWebアプリは?というと、これです。
TATEYOMITSUKURU 縦読み解読・作成アシストツールありがとうございました!
- 投稿日:2020-01-16T23:57:27+09:00
あれやこれやしてても、たった3時間ぐらいで簡単なWEBアプリを作ってUnuboで公開した話
簡単なwebアプリを1から作って、3時間ぐらいで完全無料ホスティングUnuboで公開までたどりついた話をします。
お名前で取得した独自ドメインを設定して、HTML/CSS/JavaScriptを使ったスタティックな簡単なwebアプリを公開しました。
Unuboとは
■ Unubo - Cloud deployments made easy
Unuboとは、Webアプリ、DB、ブログを完全無料でホスティングできるサービスです。更にクレカ登録不要です。(2019/1現在)完全無料のホスティングを探していて、ここあたりの情報がでてきました。
■ Node.js,Python,RubyなどのWebアプリを完全無料でホスティング可能な「Unubo」を使ってみた!
■ Unuboを使ってみた話Unuboっていうのがあるんだ〜と知ったのが、この記事を書き始めてから、昼飯挟んで約5時間ぐらい前のことです(笑)
実際に使った感想は、めちゃくちゃ簡単!しかも無料!です。
やったこと
やったことは、webアプリ作成、Unuboでの公開設定と独自ドメイン設定です。
- webアプリ作成とUnuboでの公開設定
- Unuboにサインイン
- webアプリをGitHubにPUSH
- GitHubとUnuboを連携
- デプロイ、そして公開!
Unuboは、webアプリをGithubにPUSHすると自動的にデプロイしてくれます!
- 独自ドメイン設定
- お名前でドメイン取得
- お名前でDNS設定
- Unuboにドメインを設定
webアプリをGitHubにPUSH
GitHubの使い方は、ご存知かと思いますが、以下のサイトがわかりやすかったです。
独自ドメイン設定方法
自身のアプリ管理画面のDomainsというところに独自ドメイン設定方法が書かれています。ドキュメント見てもここに書いてあるって書いてました。
独自ドメインを取得した会社のDNSレコードに、Unuboの指定するアドレス登録した後、Unuboに独自ドメインを追加します。
まずは、お名前の管理画面から取得した独自ドメインのDNS設定をしました。
- DNS設定/転送設定を開きます
- DNSレコード設定を利用するの設定するボタンを押します。
$ dig asia.unubo.app
asia.unubo.app. 34.87.61.225CNAMEを使う場合は、Aレコードを追加しないといけないとのことなので、digで「asia.unubo.app」のIPアドレスを取得してAレコードに設定しました。AレコードとCNAMEレコードを次のように設定しました。
ホスト名 TYPE TTL VALUE tateyomi.xyz A 3600 34.87.61.225 www.tateyomi.xyz cname 3600 asia.unubo.app DNSレコード設定用ネームサーバー変更確認にチェック入れて、確認画面へ進んで、設定完了。
※既存ドメインでは、設定によって既存サイトがみれなくなってしまったりもあるので、自己責任のもと、きちんと確認して設定ください。
独自ドメインがインターネットに反映されるまで待ちます。
反映されたであろう、しばらくしてから、UnuboのDomainsの②から無料SSLと一緒に独自ドメインを追加します。
独自ドメインのDNS設定後、すぐにこれを設定するも、反映されていなかったのか一向に待っても無理でした。
独自ドメインのDNS設定したら、しばらく時間をあけて、この設定をしましょう!
設定終わってほっておくと、いとも簡単にUnubo上で独自ドメインでサイト開設!となりました。Unubo、ほんとちょっとしたwebアプリをすぐ公開したいときには、ほんと便利ですね!
ちなみに公開したWebアプリは?というと、これです。
TATEYOMITSUKURU 縦読み解読・作成アシストツールありがとうございました!
- 投稿日:2020-01-16T21:16:56+09:00
[React]@babel/preset-env と @babel/ransform-runtime を core-js@3 で対応する
最近の勉強で学んだ事を、ノート代わりにまとめていきます。
主に自分の学習の流れを振り返りで残す形なので色々、省いてます。
Webエンジニアの諸先輩方からアドバイスやご指摘を頂けたらありがたいです!Babel と core-js の関係のおさらい
Babelが提供する @babel/polyfill や @babel/preset-env などのモジュールを利用すると
built-ins objects(Promise, WeakMap等)
static methods(Object.assign, Array.from等)
instance methods(Array.prototype.includes等)
といった新しい機能を使った実装が可能になりますよね。これらのBabelモジュールは core-js が提供するpolyfillを内部的に読み込んでいます。特に @babel/polyfill は core-js と regenerator-runtime を束ねて提供するpolyfill集ですcore-jsのバージョンを指定して直接読み込むます!
古いブラウザをサポートするため、core-js を利用してポリフィルを含めた React 16 向けの環境を次のように設定React 16 はコレクション型 Map および Set に依存しています。これらの機能をネイティブに提供しない(IE 11 未満など)、または標準非準拠な挙動をする(IE 11 など)古いブラウザやデバイスをサポートする場合は、core-js や babel-polyfill などのような、グローバル環境のポリフィルをバンドルしたアプリケーションに含めることを検討してください。
package.json+ "core-js": "^3.6.4",src/index.js+ import 'core-js/es/set'; + import 'core-js/es/weak-map';これで完了です!
参考記事
JavaScript 環境の要件
Babel7.4で非推奨になったbabel/polyfillの代替手段と設定方法
ReferenceError: Can't find variable: WeakMap
@babel/polyfill と core-js
- 投稿日:2020-01-16T21:05:14+09:00
Swipe.js スライド時にイベントを発火させる
Swipe.jsでiphoneライクなTimepickerを実装したので共有します。
出来上がりイメージ図は
こんな感じです。
コードはこちらから確認できます。
Timepicker using Swiper.js公式ドキュメントはあまり参考ならなかったです。(わたし的に)
Swiper補足
iphoneライクに指でスワイプできるようにすると
どうしてもイベントが発火しなかったので、
泣く泣く指スワイプは外しました。
(実装に成功した方は是非教えていただきたい)ちなみに指スワイプを実装したコードは以下になります。
index.jsvar defaults = { pagination: '.swiper-pagination', slidesPerView: 3, freeMode: true, freeModeSticky: true, freeModeMomentumRatio: 0.25, freeModeVelocityRatio: 0.25, freeModeMinimumVelocity: 0.1, mousewheelControl: true, <-- こいつと、 mousewheelSensitivity: 0.5, <-- こいつを実装する。 loop: true, loopAdditionalSlides: 5, direction: 'vertical', slideToClickedSlide: true, centeredSlides: true };
- 投稿日:2020-01-16T20:57:39+09:00
スペース派必見!絶対にインデントにはタブを使ってほしい理由
だ・である調チャレンジ!!
ソースを見やすくする工夫はいくつかある。
そのうちの1つが「インデント」だ。
このインデントについて説明する。
また、この記事でいうタブとは「ハードタブ」で、次のラインに揃えるものである。インデントとは?
JavaScriptlet a = Math.random(); if(a < 0.5){ console.log("表"); }else{ console.log("裏"); }クソコードさはどうでもいい。
とりあえず、このサンプルコードでは、見やすくするためにインデントを増やしている。
if文の{}の中が右に来ているだろう。それがインデントだ。もしこのインデントがなかったらどうなるだろう。
JavaScriptlet a = Math.random(); if(a < 0.5){ console.log("表"); }else{ console.log("裏"); }このたった数行のコードでも読みやすさがガラッと変わる。
これがもし、何百、何千行を超えるコードならどうだろう。背筋がヒヤッとする。
ソースコードにインデントは必須だ。インデントの種類
インデントの付け方は人それぞれ。
タブ派もいるがスペース派もいる。同じスペース派でも、1文字か2文字か4文字がなど、文字数も色々ある。
そしてわたしがおすすめするのは、「タブ」だ。タブインデントのメリット
キーを打つ回数が少ない
タブは、1ブロックの字下げなら1回キーを押すだけでいい。
2スペース派の2倍はやく、4スペース派の4倍速い。
そして、別に大した問題ではないのだが、今何回打ったかを気にしなくても良くなる。間にマウスカーソルが行かない
タブは1文字カウントのため、タブのなかにカーソルがいくことはない。
インデントを消すときに、スペースだと1つ残ったりする。
ストレスがたまると、継続することが困難になる。
ストレスのもとは排除すべきだ。他の人のコードを見たときに見やすくなる
タブは、エディタからすれば「\t」である。
\tは、使う人の環境によって幅が変わるから、その人にあった見え方がする。
スペースインデントは、だれが見ても同じく見えるため、自分のインデントの幅が違う人だと違和感を覚える。まとめ
インデントにタブを使うと、次のようなメリットがある。
- キー打鍵数が減る
- マウスカーソルが合う
- 他の人にコードを見せやすいメモ帳のデフォルトタブ幅が8文字なのはなぜだろうか。
別にコーディングで使いはしないが、あるYouTuberの「メモ帳でC#入門」をみてふと感じた。
設定で変更することもできるが、デフォルト時から少なくしていても、ほとんどの人は損をしないのに...
- 投稿日:2020-01-16T20:57:39+09:00
スペース派必見!絶対にインデントにはタブを使ってほしい理由まとめ
だ・である調チャレンジ!!
ソースを見やすくする工夫はいくつかある。
そのうちの1つが「インデント」だ。
このインデントについて説明する。
また、この記事でいうタブとは「ハードタブ」で、次のラインに揃えるものである。インデントとは?
JavaScriptlet a = Math.random(); if(a < 0.5){ console.log("表"); }else{ console.log("裏"); }クソコードさはどうでもいい。
とりあえず、このサンプルコードでは、見やすくするためにインデントを増やしている。
if文の{}の中が右に来ているだろう。それがインデントだ。もしこのインデントがなかったらどうなるだろう。
JavaScriptlet a = Math.random(); if(a < 0.5){ console.log("表"); }else{ console.log("裏"); }このたった数行のコードでも読みやすさがガラッと変わる。
これがもし、何百、何千行を超えるコードならどうだろう。背筋がヒヤッとする。
ソースコードにインデントは必須だ。インデントの種類
インデントの付け方は人それぞれ。
タブ派もいるがスペース派もいる。同じスペース派でも、1文字か2文字か4文字がなど、文字数も色々ある。
そしてわたしがおすすめするのは、「タブ」だ。タブインデントのメリット
キーを打つ回数が少ない
タブは、1ブロックの字下げなら1回キーを押すだけでいい。
2スペース派の2倍はやく、4スペース派の4倍速い。
そして、別に大した問題ではないのだが、今何回打ったかを気にしなくても良くなる。間にマウスカーソルが行かない
タブは1文字カウントのため、タブのなかにカーソルがいくことはない。
インデントを消すときに、スペースだと1つ残ったりする。
ストレスがたまると、継続することが困難になる。
ストレスのもとは排除すべきだ。他の人のコードを見たときに見やすくなる
タブは、エディタからすれば「\t」である。
\tは、使う人の環境によって幅が変わるから、その人にあった見え方がする。
スペースインデントは、だれが見ても同じく見えるため、自分のインデントの幅が違う人だと違和感を覚える。まとめ
インデントにタブを使うと、次のようなメリットがある。
- キー打鍵数が減る
- マウスカーソルが合う
- 他の人にコードを見せやすい
メモ帳のデフォルトタブ幅が8文字なのはなぜだろうか。
別にコーディングで使いはしないが、あるYouTuberの「メモ帳でC#入門」をみてふと感じた。
設定で変更することもできるが、デフォルト時から少なくしていても、ほとんどの人は損をしないのに...
- 投稿日:2020-01-16T19:50:35+09:00
初心者 アプリ開発
初めましてプログラミング初心者です
お店の空席確認ができるアプリを作成しています
ログインして席の番号をクリック
jsで席の番号の色が変わるように実装しましたログアウトの機能も実装してあるのですが
リロードした時に番号の色が変わらずログアウトした時のみ色が変わるような実装がしたいです
セッションを使えば実装できるのでしょうか
- 投稿日:2020-01-16T19:42:40+09:00
初心者によるプログラミング学習ログ 212日目
100日チャレンジの212日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
212日目は
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) January 15, 2020
212日目
前に作成した模写サイトにjavascript・jQueryで動きをつける試み。アウトプット重視#100DaysOfCode#駆け出しエンジニアと繋がりたい #早起きチャレンジ
- 投稿日:2020-01-16T19:37:41+09:00
WebからFetch APIで取得したファイルをJavaScriptのFileオブジェクトとして扱う方法
概要
JavaScriptのFetch APIをつかってWebからファイルをダウンロードしてきて、それをFileオブジェクト として扱う方法
やり方
const path="https://riversun.github.io/img/riversun_144.png"; fetch(path) .then(res => { return res.blob().then(blob => ({ contentType: res.headers.get("Content-Type"), blob: blob })); }) .then(data => { return new File([data.blob], path, {type: data.contentType}); }) .then(file => { //ここでfileを扱うコードを書く });ポイント
- res.blob()メソッドでレスポンスからBlobデータとして読み込むが、このメソッドはPromiseを返す
- ついでに、ヘッダーから"Content-Type"を読み出したいが、無邪気に以下のようにするとスコープ外なのでresにアクセスできない
fetch(path) .then(res => { return res.blob(); }) .then(blob => { //blobは取得できたけど、resから"Content-Type"ヘッダを取り出せない・・・ })
- 以下のようにresが同一スコープになるようblob()から下位になるようチェインして、Content-Typeまで取得してから次に渡す。
return res.blob().then(blob => ({ contentType: res.headers.get("Content-Type"), blob: blob }));
- 投稿日:2020-01-16T18:25:16+09:00
bootstrap-selectのshow/hide
検証時の環境
- bootstrap 4.1.3
- jQuery 3.3.1
- Chrome 79.0
背景
画面ロード時に,状態に応じてbootstrap-selectの表示/非表示を切り替えたく,
$('#select').show();のようにしていましたが,反映されたりされなかったりでした.
結果
以下にたどり着きました.
https://stackoverflow.com/questions/44511501/how-do-i-hide-a-bootstrap-select「bootstrap-selectは内部的に複数のbuttonに変換されるので親要素に対して適用すべし」とのことなので,適用しました.
いまのところうまくいっている様子です.
- 投稿日:2020-01-16T18:21:30+09:00
babylon.js 最初の一歩: シーンを描画する
WebGL フレームワークに Microsoft の人が作った babylon.js というものがあります。
今回はそれを使ってまず 3D シーンを描画するまでの最初の部分をどう作っていくか紹介します。
今回の完成図↓
See the Pen babylon.js 最初の一歩: シーンを描画する by 山岸 "あかいいぬ?" Masaru (@akai_inu) on CodePen.
HTML
<!-- 実際に WebGL を描画するキャンバス --> <canvas id="renderCanvas"></canvas> <!-- BabylonJS の最新バージョンへのリンク --> <script src="https://preview.babylonjs.com/babylon.js"></script> <!-- 全てのブラウザでポインタイベントに一貫性を持たせるための pep.js のリンク --> <script src="https://code.jquery.com/pep/0.4.1/pep.js"></script>依存ライブラリに pep.js というものを要求します。別になくても動作自体はしますが、ないとスマホなどでの操作がおかしくなったりします。
CSS は省略します。
JavaScript
function main() { // canvas DOM 要素を取得する const canvas = document.getElementById('renderCanvas'); // 全ての基礎となる 3D エンジンを生成 const engine = new BABYLON.Engine(canvas); function createScene() { // 新しいシーンオブジェクトを作成する const scene = new BABYLON.Scene(engine); // 自由移動出来るカメラを生成 // ドラッグで視点回転、矢印キーで移動 const camera = new BABYLON.FreeCamera('camera1', new BABYLON.Vector3(0, 5,-10), scene); // カメラの向きを座標0地点にする camera.setTarget(BABYLON.Vector3.Zero()); // canvas 要素をクリックやドラッグなどで操作出来るようにする camera.attachControl(canvas, false); // 照明を追加 var light = new BABYLON.HemisphericLight('light1', new BABYLON.Vector3(0,1,0), scene); // 球体メッシュを生成 var sphere = BABYLON.Mesh.CreateSphere('sphere1', 16, 2, scene); // 少し上に持ち上げる sphere.position.y = 1; // 地面メッシュを生成 var ground = BABYLON.Mesh.CreateGround('ground1', 6, 6, 2, scene); return scene; } const scene = createScene(); // 描画ループ関数を定義する engine.runRenderLoop(() => { scene.render(); }); // リサイズ処理 window.addEventListener('resize', () => { engine.resize(); }); } // 全ての DOM 要素が読み込み終わってから処理を始める window.addEventListener('DOMContentLoaded', main);やっていることは単純です。
- エンジンを生成
- 3Dシーンを生成
- シーンにカメラや照明、球体などを追加
- 描画ループ
という四段階でブラウザ 3D シーンの描画や操作までが可能になっています。シンプルでいいですね。
このサンプルは doc.babylonjs.com に書かれているものを少し改変したものとなっています。
また、 babylon.js は Playground を用意しているので、 CodePen などを使わなくてもより簡単に babylon.js を体験することが可能です。
createScene
という関数を定義することで、面倒な HTML やリサイズ処理などを省略して書くことが出来ます。サンプルとして 例えばこちら があります。読み込みに少し時間がかかりますが、物理演算を利用した素晴らしいデモが体験できます。
- 投稿日:2020-01-16T17:58:49+09:00
初心者による DOM イベントを使った処理-2
イベントリスナー/ハンドラーの削除
イベントリスナー/イベントハンドラーは設定することしかできなかったが、削除もできる。
イベントハンドラーの削除
イベントハンドラーはイベントリスナーよりも簡単に削除できる。これは、onXXXXメソッドに対してnull値を代入するだけである。
sample.html<form> <input id="btn" type="button" value="ダイアログ表示" /> </form>sample.jswindow.addEventListener('DOMContentLoaded', function() { let btn = document.getElementById('btn') //イベントハンドラーを登録 btn.onCLick = function() { window.alert('Hello!, Javascript!') } //イベントハンドラーを破棄 btn.onClick = null })イベントリスナーの削除
イベントリスナーの削除には、removeEventListener()メソッドを使う。
sample.jselem.removeEventListener(type, listener, capture) //elem : 要素オブジェクト type : イベントの種類 //listener : 削除するイベントリスナー capture : イベントの伝搬方向これを使って書き直すと、
saple.html<form> <input id="btn" type="button" value="ダイアログ表示" /> </form>sample.jsdocument.addEventListener('DOMContentLoaded', function() { let btn = document.getElementById('btn') let listener = function() { window.alert('Hello, Javascript') } //イベントリスナーの設定 btn.addEventLister('click', listener, false) //イベントリスナーの削除 btn.removeEventListener('click', listener, false) }, false)イベントオブジェクト
イベントリスナーやイベントハンドラーはイベントオブジェクトと呼ばれるオブジェクトを引数として受け取ります。
イベントオブジェクトを受け取るには、イベントリスナーに引数を指定するだけです。引数名は、慣例として[e]を使います。イベントオブジェクトを使用しない場合は省略しても構わない。
sample.html<form> <input id="btn" type="button" value="クリック" /> </form>sample.jsdocument.addEventListener('DOMContentLoaded', function(){ document.getElementById('btn').addEventListener('click', function(e) { let target = e.target console.log('発生元:' + target.nodeName + '/' + target.id) console.log('種類:' + e.target) }, false) }, false) //出力 //発生元:INPUT/btn //種類:clickイベント発生時のマウス情報の取得
click / mousemoveなどのイベント発生時のマウスポインターの座標を取得するには、次のようなプロパティを使います。
screenX / screenY : スクリーン上の座標 pageX / pageY : ページ上の座標 clientX / clientY : 表示領域上の座標 offsetX / offsetY : 要素領域上の座標たとえば、
sample.html<div id="main" style= "position: absolute; margin: 50px; top: 50px; left: 50px; width: 200px; height: 200px: border: 1px solid Black"></div>sample.jsdocument.addEventListener('DOMContentLoaded', function() { let main = document.getElementById('main') main.addEvenetListener('mousemove', function(e) { main.innerHTML = 'screen' + e.screenX + '/' + e.screenY + '<br />' + 'page' + e.pageX + '/' + e.pageY + '<br />' + 'client' + e.clientX + '/' + e.clientY + '<br />' + 'offset' + e.offsetX + '/' + e.offsetY + '<br />' }, false) }, false)イベント発生時のキーの情報を取得する
推されたキーの種類を取得するには、ketpress/keydownなどのキーイベントを使います。
sample..html<form> <label for="key">キー入力:</label> <input id="key" type="text" size="10" /> </form>sample.jsdocument.addEventListener('DOMContentLoaded', function() { document.getElementById('key').addEventListener('keydown', function(e) { console.log('キーコード:' + e.keyCode) }, false) }, false)ここでは押されたキーコードを出力するために、keyCodeを使っているが他にも以下のようなプロパティがある。
button : マウスのどのボタンが押されているか。 key : 押されたキーの値 keyCode : 押されたキーのコード altKey : Altキーが押されているか ctrlKey : Ctrlキーが押されているか shiftKey : Shiftキーが押されているか metaKey : Metaキーが押されているかイベント処理をキャンセルする
イベントオブジェクトのstopPropagation/stopImmediatePropagation/preventDefaultメソッドを使うことで、イベント処理を途中でキャンセルできます。
イベントの伝搬
「イベントが発生したら対応するイベントリスナーが呼び出される」がじつは、イベントが特定の要素に到達するまでにまいくつかのプロセスがある。
1. キャプチャフェーズ windowsオブジェクトから下位要素にイベントを伝搬 2. ターゲットフェーズ イベントの発生元(要素)を特定 3. バブリングフェーズ 下位要素で発生したイベントを上位要素に伝搬ここで押さえておきたいのが、「イベントリスナーはイベント発生元の要素だけで実行されるわけではない」という点だ。キャプチャ/バブリングフェーズの過程で、対応するイベントリスナーが存在する場合には、それらも順に実行される。
sample.html<div id="outer"> <p>outer要素</p> <a id="inner" href="http://www.giucgSD.sample">inner要素</p> </div>sample.jsdocument.addEvenetListener('DOMContentLoaded', function() { //<aid="inner">要素のclickイベントリスナー document.getElementById('inner').addEventListener('click', function() { window.alert('#innerリスナーが発生しました') }, false) document.getElementById('inner').addEvenetListener('click', function() { window.alert('#innerリスナー2が発生しました') }, false) document.getElementById('outer').addEvenetListener('click', function() { window.alert('#outerリスナーが発生しました') }, false) }, false)captureがfalseに設定されていると、「イベントの発生元を基準として、上位ノードへ向かって順にイベントリスナーが実行される」ので、上記の子どを実行した場合。
- #innerリスナーが発生しました
- #innerリスナー2が発生しました
- #outerリスナーが発生しました
- リンクによってページ移動
逆にcaptureがtrueに設定されていると、「上位ノードからイベントの発生元に向かって、イベントリスナーが実行される」ので、
- #outerリスナーが発生しました
- #innerリスナーが発生しました
- #innerリスナー2が発生しました
- リンクによりページ移動
となる。
イベントの伝搬をキャンセルする
たとえば、目的要素のイベントリスナーだけを実行して、上位ノードのイベントリスナーをキャンセルしたい場合があります。
このような場合に、stopPropagationメソッドを使います。sample.jsdocument.addEvenetListener('DOMContentLoaded', function() { //<a id="inner">要素のclickイベントリスナー document.getElementById('inner').addEventListener('click', function(e) { window.alert('#innerリスナーが発生しました') //<a id="inner">要素のイベントリスナーをキャンセルする e.stopPropagation() }, false) document.getElementById('inner').addEvenetListener('click', function() { window.alert('#innerリスナー2が発生しました') }, false) document.getElementById('outer').addEvenetListener('click', function() { window.alert('#outerリスナーが発生しました') }, false) }, false)これによって、
- #innerリスナーが発生しました
- #innerりすなリスナー2が発生しました
- #リンクによってページ移動
outerイベントリスナーが実行されなくなりました。
イベントの伝搬を直ちにキャンセルする。
stopPropagationメソッドが上位/下位要素への伝搬がキャンセルされるのに対して、stopImmediatePropagationメソッドは、その場でイベントの伝搬をキャンセルします。
sample.jsdocument.addEvenetListener('DOMContentLoaded', function() { //<a id="inner">要素のclickイベントリスナー document.getElementById('inner').addEventListener('click', function(e) { window.alert('#innerリスナーが発生しました') //<a id="inner">要素のイベントリスナーを直ちに!キャンセルする e.stopImmediatePropagation() }, false) document.getElementById('inner').addEvenetListener('click', function() { window.alert('#innerリスナー2が発生しました') }, false) document.getElementById('outer').addEvenetListener('click', function() { window.alert('#outerリスナーが発生しました') }, false) }, false)こうすることで、
1. #innerリスナーが発生しました
2. リンクによるページ移動となる。
イベント本来の挙動をキャンセルする
イベント本来の挙動とは、「ページ移動」や「文字の反映」などのことである。
sample.jsdocument.addEvenetListener('DOMContentLoaded', function() { //<a id="inner">要素のclickイベントリスナー document.getElementById('inner').addEventListener('click', function(e) { window.alert('#innerリスナーが発生しました') //<a id="inner">要素のイベントの本来の挙動をキャンセルする e.preventDefault() }, false) document.getElementById('inner').addEvenetListener('click', function() { window.alert('#innerリスナー2が発生しました') }, false) document.getElementById('outer').addEvenetListener('click', function() { window.alert('#outerリスナーが発生しました') }, false) }, false)これによって、
1. #outerリスナーが発生しました
2. #innerリスナーが発生しました
3. #innerリスナー2が発生しましたとなる。これで最後にページが移動しないことが確かめられる。
つまり、stopPropagation, stopImmediatePropagation, preventDefaultをすべて呼び出すことで以降のイベントの伝搬、本来の挙動をすべて止めることができる。
thisキーワード
イベントリスナーメソッドの配下では、thisはオブジェクト自身を指すように思えるが、イベントリスナーの配下ではthisはイベントの発生元を指している。
thisを正しく使うためにFunctionオブジェクトのbind()メソッドがある。sample.jsfunc.bind(that, [引数]) //func : 関数オブジェクト that : 関数の中でthisキーワードが示すものたとえば、
sample.jsdocument.addEvenetListener('DOMContentLoaded', function() { let data = { title: 'javascript', price: 2680, show() { console.log('Hello, javascript' + this.title + '/' + this.price) } } document.getElementById('btn', data.show.bind(data), false) })bindメソッドを使うことで、関数func配下のthisが引数に紐づけられる。
イベントリスナーにEventListenerオブジェクトを指定する
addEventListenerメソッドの第二引数には関数を指定してきましたが、オブジェクトも指定できます。その条件は、
handleEventメソッドを持っていること
である。handleEventメソッドはbindメソッドを考慮しなくてもよいのが利点である。
sample.jsdocument.addEventListener('DOMContentLoaded', function() { let data = { title: 'javascript', price: 2860, handleEvent: function() { console.log(this.title + '/' + this.price) } } document.getElementById('btn').addEventListener('click', data, false) })最低handleEventメソッドが含まれていればよいだけなので簡単である。呼び出す際もdataというオブジェクト名だけを指定するだけでよいのも簡単である。
また、bindメソッドを気にしなくてよい方法は、アロー関数を使うことである。
sample.jsdocument.addEventListener('DOMContentLoaded', function() { let data = { title: 'javascript', price: 2860, } document.getElementById('btn').addEventListener('click', () => { console.log(this.title + '/' + this.price) }, false) })アロー関数では、thisはアロー関数自身が宣言された場所によって決まります。
参考資料
山田祥寛様 「javascript本格入門」
- 投稿日:2020-01-16T17:04:06+09:00
Next.jsでFirebase analyticsを使用する場合の注意
- 投稿日:2020-01-16T15:45:43+09:00
vsCodeにgruntを導入してみた
概要
各種jsのcombine(結合)及びminify(難読)を行うためのツールで他にも有名どころとしてgulp、google closure compilerがある。
visualStudioCodeで作業することが多かったため、vsCodeへの導入をメモ導入手順
※導入するプロジェクトファイあるのルートを開いた状態でスタートすること
Node.jsをインストールする
(vsCodeでnpm, nodeが使用できるようになる)
(Node.js)https://nodejs.org/en/
LTSの方をインストールNode.jsがインストールできたら、vsCodeを再起動する
vsCodeのターミナルでgrunt-cliをインストール
npm install -g grunt-cliフォルダにgruntをインストール
(初回のみ gitにコミット済みであればスキップ)
場所は、プロジェクトのルートがいい(srcと同じ階層など)
npm install grunt -save-dev
grunt -Vでverが確認できればおkGruntfile.jsを作成
(初回のみ gitにコミット済みであればスキップ)/// Gruntfile.js module.exports = function (grunt) { // タスクの設定 grunt.initConfig({ uglify: {//← uglifyでないとダメ コマンド設定 options: {// オプションを設定する mangle: true,//ローカル変数名を短い名称に変更し難読化します。 compress: true//冗長なコードを短くまとめてくれます。 // beautify: true//改行、インデントは取り除かず、読みやすいように整形します。 // sourceMap:/*ソースマップも合わせて出力します。ファイルの展開先と同じディレクトリに生成されます。ソースマップは、ファイルのマッピングを解決してくれるので圧縮されたファイルでデバッグする際に便利です*/ // sourceMapName://ソースマップの生成先とファイル名を指定できます。 文字列をそのまま渡すか、関数でも渡せます。 // preserveComments: /*メントの残し方を指定します。'all' ですべてのコメントを残します'some' で /*! から始まるコメントだけ残ります。バナーやライセンス表記の際に便利です*/ // banner: //指定文字列をファイル冒頭、もしくは下部に追加した状態で生成されます // footer: //指定文字列をファイル冒頭、もしくは下部に追加した状態で生成されます }, build1: { //srcにminify化したいファイルのパス destにminify化後のパスを記載する src: 'src/js_dev/test1.js', dest: 'src/build/test1.min.js' }, build2: { src: 'src/js_dev/test2.js', dest: 'src/build/test2.min.js' } }, concat: {//← concatでないとダメ コマンド設定 js: {//← 好きな名前でどーぞ build1とかと同様 files: { 'src/build/combine.js': [// 統合ファイルのパス 'src/build/test1.min.js',// 統合元のファイルのパス 'src/build/test2.min.js' ] } } }, watch: { dev: { files: [// ←監視したいファイルのパスを設定 'src/js_dev/*.js', 'src/js_dev/*/*.js', ], tasks: ['uglify', 'concat'],// ←監視しているファイルに更新があった場合に行うタスクを設定 } } }); // モジュールの読み込み grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-watch'); // タスクの登録 grunt.registerTask('default', ['uglify', 'concat']); }; //grunt uglifyを実行するとbuild1とbuild2が実行される //grunt uglify:build1を実行するとbuild1のみ実行される //grunt を実行するとuglifyを実行した後にconcatを実行する //grunt watchを実行するとファイルの監視が始まり、監視しているファイルが更新されるたびにtasksが実行される自動更新について
1. npm install grunt-contrib-watch --save-dev(ファイル監視)
でwatchを導入し、Gruntfile.jsにwatchの設定をする2. vsCodeのコンソールにてgrunt watchを入力
C:\MyWorks\test>grunt watch Running "watch" task Waiting...のようになればOK
参考文献
・https://qiita.com/koara-local/items/1db82e3bfb3064c41862
・http://webdrawer.net/javascript/firstgrunt.html
・https://qiita.com/shuntaro_tamura/items/6cf755d408eaad8572ad
・http://blog.tsumikiinc.com/article/20140731_how-to-use-grunt-contrib-uglify.html
・http://kudox.jp/server/grunt-js
- 投稿日:2020-01-16T15:29:00+09:00
初学者は教材選びが大事
前回のあらすじ
・新人プログラマ向けの本が分かりづらすぎて学習効率が下がってた
今日やったこと
・if文とswitch文を使って、コンピューター様とじゃんけんするプログラムを作った
(今回の教科書『いちばんやさしいJavascriptの教本』)
(学習環境 エディタ→メモ帳 ブラウザ→EDGE)困ったこと
・エラーが2箇所出て、場所の特定に難航した
特定方法→ブラウザのコンソールツールを見つつ、前後4行くらいを教本と見比べていく
・原因その1
===記号のつけ忘れ
・原因その2
教本の誤植(プラス記号の抜け落ち)
本のHPを見て判明感想
古めの版年に出た本を使って学習すると、誤植に振り回されるなぁと思った。
ランダムに数を生成するメソッド「Math.random()」は応用が利きそうだなァと思った。
エラーがでたとき、落ち着いて対処できるようになりたいと感じた。次回の目標
・関数について再学習しよう
- 投稿日:2020-01-16T15:01:45+09:00
画像読み込みマネージャーのようなものを作ってみた
概要
jsで画像などを読み込む必要がある状況で、
複数データを読み込んで、ロードの終了を待ちたいというような状況がある
ロードを管理し、終了とロード状況を管理してくれる簡易的なものを作成してみた前提
main.js //制御するもの 画面ごとに用意するもの objController.js //画面に配置するオブジェクトを制御するもの loadManager.js //今回作成するロードを管理するマネージャーcreateJSにおけるロード
objController.js function createLoadQueue() { return new createjs.LoadQueue(true, null, true); } var _manifest = []; _self.loadFile = function (loaderIndex) { var loader = createLoadQueue(); loader.index = loaderIndex; loader.addEventListener("fileload", _self.handleFileLoad); loader.addEventListener("complete", _self.handleComplete); /*bitmap形式で読み込む準備 ---------------------------------------*/ _manifest.push({ kind: "bg_image", id: 1, 'src': "/aaa/bbb.png" }); /*--------------------------------------------------------------*/ loader.loaded = false; var data = { loader: loader, manifest: _manifest }; return data; } _self.handleFileLoad = function (evt) { if (evt.item.type == "image") { var bitmap = new createjs.Bitmap(evt.result); bitmap.name = evt.item.kind; _bitmapContainer[evt.item.kind][evt.item.id] = bitmap; _bitmapContainer[evt.item.kind][evt.item.id].visible = true; } } _self.handleComplete = function (evt) { var queue = evt.target; if (typeof queue.index !== 'undefined') { // 完了フラグを立てる queue.loaded = true; } }マネージャーロジック
loadManager.js var LoadManager = function () { var _self = this; var isObjLoaded = false;//loadManagerにpushされた各オブジェクトのロードが完了したかどうか var sp = 0; var loaderIndex = 0; var loaderObj = []; //ロードリスト var loadNum = 0; //ロードするファイル数 _self.push = function (obj) { var data = obj.loadFile(loaderIndex); //各オブジェクトコントローラーからローダーとロードしたいもの(マニフェスト)を受け取る obj.data = data; //オブジェクトに関連つける loaderObj[loaderIndex] = obj; // ロードリストに追加 ++loaderIndex; data.loader.loaderObj = obj; } //初期化 _self.init = function () { isObjLoaded = false; sp = 0; loaderIndex = 0; loaderObj = []; loadNum = 0; state = STATE_LOAD; frameCounter = 0; } //ロードを開始する _self.start = function () { // ロードするファイル数を取得 for (var i = 0; i < loaderObj.length; i++) { var manifest = loaderObj[i].data.manifest; loadNum += manifest.length; } createjs.Ticker.addEventListener('tick', update); } //update function update() { diffTime = getDiffTime(); switch (state) { case STATE_LOAD: // 読み込み終了 if (isLoadEnd()) { state = STATE_LOAD_END; frameCounter = 0; break; } nextLoad(); break; case STATE_LOAD_END: if (frameCounter == 1) { isObjLoaded = true; createjs.Ticker.removeEventListener('tick', update); } break; } ++_frameCounter; } //ロードが終了したかどうか function isLoadEnd() { var loaded = true; for (var index in loaderObj) { loaded = loaded && loaderObj[index].data.loader.loaded; } return loaded; } //次のロードを開始する function nextLoad() { // 初回もしくは、一つ前のロードが終わっていたら次のロードを始める if (sp == 0 || loaderObj[sp - 1].data.loader.loaded) { var loader = loaderObj[sp].data.loader; var manifest = loaderObj[sp].data.manifest; for (var i = 0; i < manifest.length; ++i) { manifest[i].loadTimeout = 50000; } if (manifest.length != 0) { loader.setMaxConnections(5); loader.loadManifest(manifest); } else { loader.loaded = true; } sp++; } } }使用イメージ
main.js //初期化して、ロードしたいオブジェクトをpushする var loadManager = new LoadManager(); loadManager.init(); obj1 = new objController(); obj2 = new objController(); obj3 = new objController(); loadManager.push(obj1); loadManager.push(obj2); loadManager.push(obj3); //ロードを開始する loadManager.start(); //ロードの終了を判定する loadManager.isLoaded();
- 投稿日:2020-01-16T14:25:23+09:00
【初心者】Vue.jsでタブメニューを作ってみた
最近、Vue.jsを勉強しています。
練習として、タブメニューをつくってみました。Vue.jsの導入
以下のコードでVue.jsを導入します。
HTMLファイルのbody内に記述します。
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
JavaScriptを記述する
HTMLファイルのbody内のscriptタグ内に、以下のコードを記述します。
new Vue({ el: '#app', data: { active: 1 }, methods: { change: function(num) { this.active = num; } } })変数
active
という変数を用意しています。
どのタブが選択されているのかを代入します。関数
change
という関数を用意しています。
他のタブが選択されたときに用います。
active
に、選択されたタブの番号を代入します。HTMLを記述する
HTMLファイルのbody内に、以下のコードを記述します。
<div id="app"> <ul id="tabMenu"> <li v-on:click="change(1)" v-bind:class="{'active': active === 1}">タブ1</li> <li v-on:click="change(2)" v-bind:class="{'active': active === 2}">タブ2</li> <li v-on:click="change(3)" v-bind:class="{'active': active === 3}">タブ3</li> </ul> <p v-if="active === 1">コンテンツ1</p> <p v-else-if="active === 2">コンテンツ2</p> <p v-else-if="active === 3">コンテンツ3</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script>
v-on
,v-bind
,v-if
,v-else-if
を使いました。v-on
v-on:click="change(2)"
は、その要素がクリックされたとき、
関数change
に値2
を渡して処理を行います。v-bind
v-bind:class="{'active': active === 2}"
は、
active === 2
が真のとき、その要素でclass="active"
を有効にします。v-if, v-else-if
v-else-if="active === 2"
は、
そのすぐ上に書いてあるv-if="active === 1"
の続きになります。
active === 1
が偽で、active === 2
が真のとき、実行します。CSSを記述する
見た目を整えます。
#tabMenu { padding: 0; display: flex; list-style-type: none; } #tabMenu li { width: auto; padding: 10px 20px; color: black; border: 1px solid black; background-color: white; cursor: pointer; } #tabMenu li.active { color: white; background-color: black; transition: .3s; }ぶっちゃけ、CSSが一番難しかったです。笑
flexbox
は便利なので、使いこなせるようになりたいです。終わりに
Vue.jsのアウトプットの練習として、タブメニューを作ってみました。
割と簡単に作ることができたので、Vueってスゴいなと思いました(小並感)
引き続き、Vueの勉強をがんばります。
- 投稿日:2020-01-16T14:21:50+09:00
画像をアップロード前に圧縮する流行りの方法【Vue.js x Firebase x 令和】
こんにちは。年末年始はずっとFirebaseを触っていました @ykhirao です。たぶん3年くらい遅れて流行に乗り始めています。Typescriptお正月にはじめました。
今日は、仕事がお休みなのでスマホぽちぽちしていたらすごく参考になる記事を見かけましたのですが、こちらの記事 Firebaseで作る!リアルタイム画像変換CDN【Firebase Hosting + Cloud Functions】 - AppBrew Tech Blog で書かれている
アップロードされた画像をそのまま表示する時代は平成とともに終わりを告げたわけですが[※要出典]
という文言を見てくすっと笑ってしまい、少し前に 君はまだ平成のアーキテクチャを使ってるのか?僕はFirebaseと令和の時代に行くぞ。 という記事がたくさんの方に読まれていたこととか、Twitterの擬人化された #令和ちゃん を思い出したりして、エンジニアのみなさんは令和ちゃんもFirebaseも大好きっぽいので私も記事を書こうかなーーと思って書き始めました。ネタなんだけど誰かの役にたつ記事を書きたい次第です。さて前置きが長くなりましたが、本文は、画像をアップロードする前にゴリッと処理する方法を書いて行きます。
画像をアップロード前に圧縮する流行りの方法
今回紹介すること、しないこと
# 画像アップロードについての手法 1. 画像処理をしない(平成) 2. 画像処理をする(令和) - 2.1 サーバーで処理する (https://tech.appbrew.io/entry/2020/01/15/120000 で紹介されている) - Firebase Extension(まだβっぽいですが公式の拡張があるっぽい!) - CloudFunctionsを自分でNode.JSごりごり書く - 2.2 クライアント側で処理する( <---- 今日の記事はここ!!!)本文いらないからスクショとかGistみたいひと
以下のスクショのようになるGistはこちら
https://gist.github.com/ykhirao/1d36aeca9abb02709cc9dd3d676040ef(最大 100x100 px にしているので2kbくらい)
処理して簡単に解説する
Canvasでごりっと処理するとか、ライブラリに頼るとか方法はあると思いますが今回は compressorjs を紹介したいっす。
component.jsimport Compressor from 'compressorjs'OR
index.html<script src="https://cdn.jsdelivr.net/gh/fengyuanchen/compressorjs/dist/compressor.min.js"></script>CDNはなかなか見つからなかったのですがぐぐったら このブログ で見つかった。まさとらんさんありがとうです。
画像は
input
タグで選択してもらって<input type="file" id="file" multiple accept="image/*" @change="handleFiles" />
multiple
で複数選択可にしてaccept
で画像以外を一応弾いています。それで@change
イベントのフックで画像を圧縮しています。handleFiles(e: Event) { const images = this.images const files: FileList | null = (<HTMLInputElement>e.target).files if (files === null) return ;[...files].forEach((file: File) => { // 複数ファイルを受け入れているので、この中で処理している }) }複数処理やfileが取得できなかった場合の処理は上のような感じで、Typescript的にエラーが出ていないのでこれでOKかなと思ってます(認識あってますか?)
const payload: Compressor.Options = { quality: 0.8, maxWidth: 100, maxHeight: 100, mimeType: 'image/jpeg', success(blob: Blob): void { // ここに成功時の処理を書く。次。 }, error(err: Error): void { console.log(err.message) } } new Compressor(file, payload)実際の処理はこんな感じで横幅・縦幅の最大とか圧縮率とかたくさんの オプション があります。また成功時と失敗時は
success
とerror
のコールバックに書くみたいです。success(blob: Blob): void { },サクセスのコールバックは
Blob
を受け取るのでFirebaseではこの段階でファイルのアップロードができます!!!ドキュメントはこちら。 Blobからアップロードできるとかすごくいいですね。(試してないけどたぶんそちらでUploadできる)今回はアップロードする前に読み込みを確認したかったので、DataUrlという形式に変換しています。
success(blob: Blob): void { var reader = new FileReader() reader.onloadend = () => { const result = <string | ArrayBuffer | null>reader.result if (result instanceof ArrayBuffer || result === null) return images.push(result) } reader.readAsDataURL(blob) },
images.push(result)
する段階でDataUrlという形式のdata:image/jpeg;base64,XXXXXXXX
みたいな文字列になっています。ドキュメントの文字列からアップロードするを見るとvar message = 'data:text/plain;base64,5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; ref.putString(message, 'data_url').then(function(snapshot) { console.log('Uploaded a data_url string!'); });これでアップロードできそうですね。
ドキュメントを読むと
data:text/plain;base64,
という文字列を削除したら ref.putString(message, 'base64url')でアップロードできそうです。たぶん。サンプルファイルは
<script lang="ts">
にしているのでVSCodeでひらいて、CMD + Click
とかで定義元とか飛んで行って自分で触ってみてください!!終わりに
ブラウザのAPIを叩くときにTypeScriptすごく開発体験良かったです。MDNを見に行かなくても何が返ってくるかわかるので、null制御とかそういうのがやりやすかったです。コード書いてて楽しかった。
なにか間違い見つけたら編集リクエストください!!!
最後まで読んでいただきありがとうございます。!!
- 投稿日:2020-01-16T12:11:31+09:00
とりあえずreact-hook-form
はじめに
react系のフォームバリデーションライブラリreact-hook-formのざっくり使い方です。
公式のドキュメントでは、
- 超軽量なパッケージ
- 再レンダリングを最小に押さえて、マウントの高速化
- フォームの値がローカル管理される為、他パッケージに依存しない
等々の利点が挙げられている。
formikとの比較
download
https://www.npmtrends.com/redux-form-vs-formik-vs-react-hook-formsize
formik
react-hook-form
API
useForm
useFormでいろいろなapiを受け取れる
useFormのoption(default)const { register } = useForm({ mode: 'onSubmit', // | 'onBlur' | 'onChange' reValidateMode: 'onChange', // onBlur | onSubmit 再度バリデーションされるタイミング defaultValues: {}, validationSchema: {}, // スキーマレベルで Yup を使用してフォームバリデーションルールを適用 validateCriteriaMode: "firstErrorDetected", submitFocusError: true, // エラーのある最初のフィールドがフォーカスされる nativeValidation: false, // ブラウザバリデーションの活用 })register
input/selectのRefとバリデーションルールをreact-hook-formに登録する
<input name="form1_1" defaultValue="test" ref={register} />registerでバリデーションをかけられる。下記のコードではrequired, minLengthを5にせっていしている。
'1_2は必須です', '5桁以上必要です'はエラーメッセージ。<input name="form1_2" ref={ register({ required: '1_2は必須です', minLength : { value: 5, message: '5桁以上必要です' } }) } />フィールドフォームネストして扱うこともできる
name output name="firstName" { firstName: 'value' } name="firstName[0]" { firstName: [ 'value' ] } name="name.firstName" { name: { firstName: 'value' } } name="name.firstName[0]" { name: { firstName: [ 'value' ] } } errors
errorsオブジェクトにはフォーム内の各フィールドのエラーオブジェクト。
react-hook-formにはエラーメッセージ用の表示にErrorMessageコンポーネントも用意されている。import { useForm, ErrorMessage } from 'react-hook-form' // ~~ <ErrorMessage errors={errors} name="form1_2" /> // ~~watch
指定されたnameのinputを監視して、その値を返す。
defaultValue が定義されていない場合、watch の初回のレンダリングは register の前に呼び出されるため undefined を返しますが、 第2引数として defaultValue を設定して値を返すことができます。
ただし、引数として useForm で defaultValues が初期化された場合、 初回のレンダリングは defaultValues で指定された値を返します。
<p className="form1-watch-text">watch output: {watch('form1_1')}</p>handleSubmit
フォームバリデーションを通るとデータを渡す。
// ~~ const onSubmit = (data: Object) => { console.table(data) }; // ~~ <input type="submit" /> // ~~async関数も渡すことができるhandleSubmit(async (data) => await fetchAPI(data))Form1import React from 'react'; import { useForm, ErrorMessage } from 'react-hook-form' export default function Form1() { const { register, handleSubmit, watch, errors } = useForm({ validateCriteriaMode: 'all' }); const onSubmit = (data: Object) => { console.table(data) }; return ( <div className="form form1"> <h1>Form1</h1> <form onSubmit={handleSubmit(onSubmit)}> <div className="form-section"> <span>1_1: </span> <input name="form1_1" defaultValue="test" ref={register} /> <span className="sub-text">*watched</span> </div> <div className="form-section"> <span>1_2: </span> <input name="form1_2" ref={ register({ required: '1_2は必須です', minLength : { value: 5, message: '5桁以上必要です' } }) } /> <span className="sub-text">*required</span> </div> <div className="form1-watch"> <p className="form1-watch-text">watch output: {watch('form1_1')}</p> </div> <div className="errors"> <ErrorMessage errors={errors} name="form1_2" /> </div> <input type="submit" /> </form> </div> ) };controller
Controllerコンポーネント(UIコンポーネントライブラリと併せて使用するコンポーネント)用。
コンポーネント登録するためのメソッドが含まれている。
以下ではmaterial-uiを使用。Form2import React from 'react'; import { useForm, Controller, ErrorMessage } from 'react-hook-form'; import { TextField, Button } from "@material-ui/core"; export default function Form2() { const { handleSubmit, errors, control } = useForm() const onSubmit = (data: Object) => { console.table(data) }; return ( <div className="form form2"> <h1>Form2</h1> <form onSubmit={handleSubmit(onSubmit)}> <Controller as={<TextField />} name="form2_1" control={control} rules={{ required: "必須です" }} defaultValue="" /> <div className="errors"> <ErrorMessage errors={errors} name="form2_1" /> </div> <Controller as={<Button color="primary" ><span>送信</span></Button>} name="submit" control={control} defaultValue="" onClick={handleSubmit(onSubmit)} /> </form> </div> ) }reset
フォーム内のvaluesとerrorsをリセットできる関数。
リセット時に値を渡すとデフォルトの値としてリセットできる。
Form3import React, { useCallback } from 'react'; import { useForm, ErrorMessage } from 'react-hook-form' type Reset = (values?: Record<string, any>) => void; export default function Form3() { const { register, handleSubmit, reset, errors }: ({ register: Function, handleSubmit: Function, reset: Reset, errors: any, }) = useForm(); const onSubmit = (data: Object) => { console.table(data) }; const onReset = useCallback(() => reset(), [reset]) const onDefaultReset = useCallback(() => reset({ first_name: 'ジョン', last_name: '万次郎' }), [reset]) return ( <div className="form form3"> <h1>Form3</h1> <form onSubmit={handleSubmit(onSubmit)}> <span>姓:</span> <input name="last_name" ref={register({ required: '姓は必須です。' })} /> <div className="errors"> <ErrorMessage errors={errors} name="last_name" /> </div> <span>名:</span> <input name="first_name" ref={register({ required: '名は必須です。' })} /> <div className="errors"> <ErrorMessage errors={errors} name="first_name" /> </div> <input type="submit" /> <input type="button" onClick={onReset} value="reset" /> <input type="button" onClick={onDefaultReset} value="reset + default set" /> </form> </div> ) };setError / clearError
inputのエラーを手動で設定したりクリアする。
setValue
値を動的に設定できる。
Form4import React, { useCallback } from 'react'; import { useForm, ErrorMessage } from 'react-hook-form' export default function Form4() { const { register, handleSubmit, setError, clearError, errors, setValue } = useForm(); const onSubmit = (data: any) => { const goodAnswer = 12 * 12; if (parseInt(data.answer, 10) !== goodAnswer) return setError('answer', 'notMatch', '不正解です'); clearError('answer'); console.log('正解'); }; const onSetValue = useCallback(() => { setValue('answer', 12 * 12); }, [setValue]); return ( <div className="form form4"> <h1>Form4</h1> <form onSubmit={handleSubmit(onSubmit)}> <span>12 × 12 = </span> <input name="answer" ref={register({ required: '入力してください' })} /> <div className="errors"> <ErrorMessage errors={errors} name="answer" /> </div> <input type="submit" value="回答" /> <input type="button" value="諦める" onClick={onSetValue} /> </form> </div> ) };getValues
フォーム全体のデータを返す。
<input name="test" ref={register} /> <input name="test1" ref={register} /> <button type="button" onClick={() => { const values = getValues() console.log(values) // e.g: { test: [1, 2], test1: { data: '23' } }} > GetValues </button>triggerValidation
バリデーションのトリガーを手動で設定できる。
<input name="lastName" ref={register({ required: true })} /> <button type="button" onClick={async () => { triggerValidation("lastName"); }} > Trigger </button>unregister
inputにunregisterを適用すると、フォームデータに含まれなくなる。
これは、 useEffect でカスタム登録として input を登録 (register) し、 コンポーネントのアンマウント後に登録を解除する場合に便利です。
らしい。
formState
フォームの状態がオブジェクトで入っている。
name description dirty 入力が行われた後trueになる isSubmitted submitされた後trueになる isSubmitting 送信中はtrue, 送信後falseになる touched 操作されたnameが配列で入る submitCount submitされた回数 isValid errorがない場合true おわりです。込み入ったことは試していないのですが、記述が簡単な印象でした。
- 投稿日:2020-01-16T11:55:05+09:00
【PHP】stripe API で定額課金を実装する
はじめに stripeとは
stripeは、クレジットカードなどの決算処理を代行してくれるサービスです。
シンプルな実装で決済処理を実現でき、カード情報を自社サーバで持たなくても良いなどの特徴があります。大まかな流れとして、
- サービスの請求モデルを作成
- フロントサイドでカード情報を取得
- バックエンドで顧客登録、決済処理
と進んでいきます。
サービスの請求モデルを作成する
1. stripeに登録する
1-1.はじめに、stripeの公式ページにて、今すぐ始めるをクリック。
1-2メールアドレス、名前、パスワードを入力登録。
なお、テスト用に登録する際には、メールアドレスの存在確認は求められません。
この時点で、登録が完了いたしました。
1-3.ダッシュボードが表示される
登録が完了したら、いくつかの質問がされるので答えていきます。
質問が完了したら、次のようなダッシュボードが表示されます。
2.テストAPIキーを取得する
ダッシュボードが表示されたら、まずはAPIキーを取得してみましょう。
今回はテスト用のAPIキーを取得します。
ダッシュボードのテストAPIキーの取得をクリックしてください。公開可能キー
公開可能キーは、不特定多数に表示されてもかまわないキーです。
JavaScriptなど、フロントサイドで使用されるキーです。シークレットキー
一方シークレットキーは秘密にしなければなりません。
PHPなど、サーバーサイドで使用されます。3.商品(Product)を登録する
3-1.Billing->商品をクリック
はじめに、提供するサービスである、商品(Product)を登録します。
画面左側のBilling
をクリックしてから、商品
が表示されるのでもう一度クリックしてください。3-2.商品を作成
なお、ここでは商品の価格などの情報は入力しません。
商品の配下に、プラン(Plan)
を作成して個別に料金体系(価格、通貨、支払い間隔など)を設定します。
一つの商品に対して、複数のプランが設定できます。
例えば、有料会員
という製品には以下のプランが紐付けられていると考えられます。
- 有料会員
- ブロンズ会員 月額500円
- シルバー会員 月額1,000円
- ゴールド会員 月額3,000円
4.プランを作成する
4-1.プランの作成
それでは実際にプランを作成してみます。
商品を作成したら、そのままプランを作成するページへ遷移します。
必要な項目を入力して、
料金プランを追加
をクリックすると、プランが追加されます。4-2.プランIDの取得
後ほど会員をプランに追加する際に、プランIDが必要になるのでここで取得します。
prod_から始まる商品IDではないことに注意してください
先程作成したプランクリックして詳細を表示します。
コードを実装する。
5.ライブラリをインストールする
いよいよコードの実装にはいります。
はじめに、Composer
を利用して、PHPのライブラリを入手します。composer require stripe/stripe-php6.フロントサイドの実装(JavaScript)
6-1.カード情報を入手する
カード情報を入手するための入力フォームを、JavaScritpで実装します。
カード情報といっても、実際に得られるのはAPI通信によって得られたトークンの情報です。index.html<form id="form_payment" action="/add.php" method="post"> Name:<input id="name" type="text" name="name"> Email:<input id="email" type="text" name="email"> <!-- ここのdivタブがカード入力フォームに置き換わります。 --> <div id="card-element" class="MyCardElement"></div> <!-- ここにエラーメッセージが表示されます。 --> <div id="card-errors" role="alert"></div> <button id="button">Submit</button> </form> <!-- stripAPIを読み込みます --> <script src="https://js.stripe.com/v3/"></script> <script> // 公開可能なAPIキーです。 const stripe = Stripe('pk_test_aGucPIXrlDAHQuflip6RqErD00CqmOnlKK'); // 入力フォームを生成します。スタイルを指定することもできます。 const elements = stripe.elements(); const cardElement = elements.create("card"); // 先程のdivタブにマウントします。 cardElement.mount("#card-element"); // クレジットカード番号や有効期限の入力に合わせてエラーメッセージを出力します。 cardElement.addEventListener('change', ({error}) => { const displayError = document.getElementById('card-errors'); if (error) { displayError.textContent = error.message; } else { displayError.textContent = ''; } }); const submit = document.getElementById('button'); const name = document.getElementById('name'); const email = document.getElementById('email'); // 登録ボタンがクリックされたら、API通信をおこなう submit.addEventListener('click', async(e) => { e.preventDefault(); const {paymentMethod, error} = await stripe.createPaymentMethod({ type: 'card', card: cardElement, billing_details: { // 顧客名emailアドレスはなくてもOK name: name.value, email: email.value, }, }); // 通信エラー時 if (error) { console.error(error) } else { // 成功したらトークンが返されるので、hiddenに埋め込む const form = document.getElementById('form_payment'); const hiddenToken = document.createElement('input'); hiddenToken.setAttribute('type', 'hidden'); hiddenToken.setAttribute('value', paymentMethod.id); hiddenToken.setAttribute('name', 'token'); form.appendChild(hiddenToken); form.submit(); } }); </script>下記画像のように、カード番号・有効期限・確認番号・郵便番号を入力するフォームが生成されました。
これらの項目はすべて必須項目です。
もし、郵便番号の入力をなくしたいなら、入力フォーム生成時にhidePostalCode
をtrue
にします。const cardElement = elements.create('card', {hidePostalCode: true});テスト用として、
4242 4242 4242 4242
の番号が用意されているので、そちらを利用します。
その他の値はなんでもかまわないようです。7.サーバーサイド(PHP)
7-1.顧客情報を登録する。
フロントサイドから受け取ったトークンをもとに、顧客情報を登録します。
<?php require '/vendor/autoload.php'; // シークレットキーをセットする \Stripe\Stripe::setApiKey(YOUR_SECRET_KEY); if($_SERVER['REQUEST_METHOD'] == 'POST') { // ここではバリデーションは省略しています $name = $_POST['name']; $email = $_POST['email']; $token = $_POST['token']; // 顧客情報を登録 $customer = \Stripe\Customer::create([ 'payment_method' => $token, // 登録する支払い方法 'name' => $name, 'email' => $email, 'invoice_settings' => [ 'default_payment_method' => $token, // デフォルトで使用する支払い方法。必須。 ], ]);7-2.顧客をプランに登録する
さらに、先程登録した顧客情報をもとに、プランへの登録処理を行います。
// 顧客をプランに登録する $subscription = \Stripe\Subscription::create([ // 先程登録した顧客情報のID 'customer' => $customer->id, 'items' => [ [ // 4-2で取得したプランID 'plan' => 'plan_GYHnkoAjA7PFtG', ], ], 'trial_end' => strtotime('+3 month'), // トライアル(無料)期間。UNIX秒で指定する。 ]); // subsctiprionIDは、解約時に必要 $sub_id = $subscription->id; // sub_GYJXuOff81PbZy // DBを準備 try { $db = new PDO('mysql:dbname=php;host=localhost', 'root', ''); } catch (PDOException $e) { print "Coudn't connet to the database: " . $e->getMessage(); exit(); } $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 顧客情報を登録 try { $stmt = $db->prepare('INSERT INTO Users (name, email, sub_id) VAlUES (?,?,?)'); $stmt->execute($name, $email, $sub_id); } catch (PDOException $e) { print "Coudn't connet to the database: " . $e->getMessage(); exit(); } }
trial_end
を指定すると、トライアル(無料期間)を定めることができます。
音楽ストリーミングサイトによくある初回3ヶ月無料!みたいなやつです。UNIX秒で指定するので、strtotimeを使用すればある程度簡単に指定することができます。
プラン登録時の返却値の
Subscription
オブジェクトのIDは、解約時に必要になります。
なので、顧客情報とともにDBに登録します。8.登録を確認する
ダッシュボードに戻り、左側の
顧客
をクリックすると、先程登録した顧客情報が表示されているかと思います。
無事顧客が登録されていましたね。
さらに、詳細情報をみると顧客の定期支払を確認することができます。
トライアル期間を設定したので、請求書をみると初回の支払いは0円で、次回請求日は3ヶ月後に設定されていることが確認できます。(今回登録した日付は01/15)
9.プランを解約する
最後に、顧客が有料会員プランを解約する流れを説明します。
<?php // 契約を解除するためにログインしている想定 session_start(); require 'vendor/autoload.php'; $name = $_SESSION['name']; // DBを準備 try { $db = new PDO('mysql:dbname=php;host=localhost', 'root', ''); } catch (PDOException $e) { print "Coudn't connet to the database: " . $e->getMessage(); exit(); } // ログインユーザーからsub_idを取得 $stmt = $db->prepare('SELECT sub_id FROM Users WHERE name = ?'); $stmt->execute($name); $row = $stmt->fetch('assoc'); if ($row) { $sub_id = $row->sub_id; } else { exit(); } $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // シークレットキーをセットする \Stripe\Stripe::setApiKey(YOUR_API_KEY); // 解約したあとも有効期限までは、プランに入会したまま \Stripe\Subscription::update( $sub_id, [ 'cancel_at_period_end' => true, ] );上記の例のように
cancel_at_period_end
をtrue
にしてプランをSubscription::update
で更新することで、解約後も有効期限内まではプランが有効になります。もし、解約後はすぐにプランを終了したいときには、以下のようなコードを書きます。
// 解約後はすぐにプランが終了する。 $subscription = \Stripe\Subscription::retrieve('sub_49ty4767H20z6a'); $subscription->cancel();8-2.解約を確認
ダッシュボードを確認すると、先程の会員が有効期限である
04/15
にプランがキャンセルされることが確認できました。
おわりに
定額課金の決済の簡単な流れを説明しました。
他にも期間中のアップグレード、ダウングレードや途中解約に対する比例配分、更新時にカードが利用できなかった場合など複雑な状況にも対応できます。ぜひ、ドキュメントも参照してみてください。
参考サイト
公式ドキュメント
API リファレンス
Stripe Billing 101
Stripe.js & Elements を利用して決済フローを理解する
- 投稿日:2020-01-16T11:03:19+09:00
setTimeoutを使って指定時間後に処理を行う際はスリープに注意
setTimeoutで数秒ではなく数十分など長めに設定した場合に、上手く挙動しなかったことがありました。
setTimeout
指定時間後にJavaScript関数を実行するsetTimeout。
ページを開いたまま20分経過したら自動で指定ページに遷移させようとしました。setTimeout(function(){ window.location.href = 'https://xxx.ne.jp/'; }, 20 * 60 * 1000);普通思い浮かぶであろうこのコードでなんの問題もなく処理が動きそうです。(と思ってました...)
ただ、このsetTimeoutはAndroidやiOSといったスマホ・タブレット端末のWebブラウザでは注意する必要があります。
スマホ・タブレット端末では、アプリがバックグランドに入るスリープの概念があり、この状態ではsetTimeoutの呼び出し(JavaScriptの実行そのもの)も保留されてしまいます。
スリープ中に止まったタイマーが完了するのは、1分後かもしれないし10分後、1時間後かもしれません...。経過時間をチェックしてスリープ対策
スマホユーザーが多いサービスがゆえにスリープ対策をどうしてもしなければならなかったので、setIntervalで「ページを開いてから20分経過したか」を定期的にチェックし続ける処理を入れることにしました。
- setTimeout関数 ・・・ 指定した時間経過後に処理を実行する
- setInterval関数 ・・・ 指定した時間ごとに処理を実行する
- clearTimeout関数 ・・・ setTimeoutで設定したタイマーを取り消す
- clearInterval関数 ・・・ setIntervalで設定したタイマーを取り消す
※時間の指定単位は「ミリ秒」
// 20分後指定URLにリダイレクト const setTime = Date.now() + (20 * 60 * 1000); const timer_Id = setInterval(() => { if (Date.now() > setTime) { clearInterval(timer_Id); window.location.href = 'https://xxx.ne.jp/'; } }, 500);この処理だと、途中でスリープしていた場合でも
- ページを開いてから20分後に処理が走る
- スリープ中に20分を過ぎていたら復帰した瞬間に実行される
今回は1秒だと微妙だったので0.5秒(500ミリ秒)でIntervalを指定しました。
最後に
「Webブラウザがバックグラウンドに回ってスリープ状態になり処理が止まる」という事象はPCでは起こりにくいので、AndroidやiOSで挙動が変わることがあるという点は注意すべきところですね。
- 投稿日:2020-01-16T09:27:47+09:00
[Vue.js] ケバブケースとかキャメルケースとかパスカルケースとか
Vueでのケースの書き方って場所によって何がいいか若干悩みますよね。(私だけ?)
なので、まとめてみました。そもそも○○○ケースって?(復習)
一言で言うと「クラス名や変数名等の名前の付け方の総称」
覚えておくべきケースをざっと説明すると。。ケバブケース
文字と文字の区切りを
-
で表現するやり方。「チェインケース」とも言う。
ケバブのお肉をぶっさしている感じからきているとか。this-case-is-godキャメルケース
文字と文字の区切りを大文字で表現するやり方。
よく見るやつです。ラクダのこぶですね。thisCaseIsGodパスカルケース
文字と文字の区切りを大文字で表現+先頭の文字も大文字にする。
それもそのはず、「アッパーキャメルケース」とも呼ばれるのだから。
これもよく見ますね。ThisCaseIsGodどこにどのケースを書くべきか?
基本的に下記の方針でOK
- コンポーネントは「パスカルケース」
- JavaScriptでは「キャメルケース」
- HTMLでは「ケバブケース」
記述に迷う主な例を挙げたいと思います。
コンポーネントは「パスカルケース」
<template> <MyComponent></MyComponent> </template>因みに、componentsに指定する際にパスカルケースであれば、ケバブケースでも記述できるが
ケバブケースで指定するとケバブケースでしか動かない。<template> <MyComponent></MyComponent> <!-- OK --> <my-component></my-component> <!-- OK --> <MyComponent2></MyComponent2> <!-- NG --> <my-component2></my-component2> <!-- OK --> </template> <script> export default { components: { MyComponent, 'my-component2': MyComponent2, } } </script>props 属性は「ケバブケース」 その他は「キャメルケース」
<template> <MyComponent :my-data="hoge"></MyComponent> </template> <script> export default { props: ['myData'], } </script>emitは「ケバブケース」
JavaScript内だが文字列として定義しているだけで、
カスタムイベント名として使われるためケバブケース推奨<script> export default { methods: { hogeFunc() { this.$emit('my-emit') } } } </script>最後に
書き終わった後に気づいたのですが、とても分かりやすい記事がありました。
参考にさせていただきます。
https://qiita.com/ngron/items/ab2a17ae483c95a2f15e
- 投稿日:2020-01-16T08:54:30+09:00
npm を使用して、誰かが書いたコードをリユースする方法
npmで誰かが書いたコードを使用する
NPM(Node Package Manager)は、誰かが書いたコードを使用できる便利な管理システム。
自分で1から作るんじゃなくて、誰かが書いたコードを使用できるなら、それをリユースして無駄な時間を削減しようっていう考え方を元に作成された管理システム。
npm を使用するまでの手順
今回は intro-to-node のフォルダ内のindex.jsというファイルでnpmを使用できるようにします。
npm を初期化する
コマンドラインでちゃんと intro-to-node フォルダに移動しているか確認してください。
確認できたら
npm initで初期化します。
色々入力を求められるので、こんな感じで入力していきます。
まぁほとんどコマンドラインの提案通りにEnter押していくだけで、実際入力したのは、description(説明)とauthor(著者)のみ。
すると、package.jsonファイルが intro-to-nodeフォルダに作成されているのがわかります。
ちなみに、package.jsonの中身はこんな感じ。
package.json{ "name": "intro-to-node", "version": "1.0.0", "description": "This is a introduction to node project.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "kibinag0", "license": "ISC" }npm で使用したいパッケージを選ぶ
npm の公式サイト
(https://www.npmjs.com/)
にいって、使用したいパッケージを選ぶ。今回使うのは、下記のスーパーヒーローの名前を取得してくれるパッケージです。
https://www.npmjs.com/package/superheroes『superheroes name』 と検索したら、出てきました。
使用するパッケージをインストールする
各パッケージには、インストールの部分があるので、コマンドラインを使用してインストールします。
ちゃんと自分が使用するフォルダ(intro-to-node)にいるか確認してから、インストールを実行しましょう。
『npm install パッケージ名』
これでインストールは完了。
パッケージを使用する
使用するときは、パッケージのUsageを見るとわかりやすいです。
index.js// superheroes をファイル内で使用できるようにする const superheroes = require("superheroes"); // Usage に記載されている random() を使用する var hero = superheroes.random(); // ランダムで Super Hero の名前を取得できるこれにてnpmの使用方法まとめ終了です。
このコンテンツはUdemyの The Complete 2020 Web Development Bootcamp を参考にしています。
- 投稿日:2020-01-16T05:45:35+09:00
【Firebase】【Firestore】名前にドットや空白など記号を含むフィールドで検索する
Firestoreではフィールド名にドットや空白など記号を含めて保存することができます。
let db = firebase.firestore(); const testdata = db.collection("data").doc("test"); const doc = {normal:true, map:{}}; doc['dot.test'] = true; doc.map['normal'] = true; doc.map['space test'] = true; doc.map['123456'] = true; db.collection("data").doc("test").set(doc);保存されたデータ↓
これらのフィールドをwhere条件にしてクエリを実行します。
通常のフィールド名(アルファベット&数字&アンスコのみ、かつ数字開始ではない)の場合、フィールド名をそのまま文字列で指定すればOKです。
let query = db.collection('data').where('normal', '==', true); let querySnapshot = await query.get(); querySnapshot.forEach(function(dataRef){ console.log(dataRef.id, dataRef.data()); });入れ子の場合はドット区切りで指定
query = db.collection('data').where('map.normal', '==', true);ここからが本題。
ドットや空白などが含まれるフィールドをそのまま検索してみます。query = db.collection('data').where('dot.test', '==', true);query = db.collection('data').where('map.dot.test', '==', true);これだと結果が返ってきません(空白を含むフィールドも同様)。
ドキュメントを見ると下記の様に書かれています。
Constraints on field paths
* Must separate field names with a single period (.)
* Must enclose each field name in backticks unless the field name meets the following requirements:
- The field name contains only the characters a-z, A-Z, 0-9, and underscore (_)
- The field name does not start with 0-9言われるがままバッククォートで囲んでみます。
query = db.collection('data').where('`dot.test`', '==', true);しかしこれでも結果は返ってきません。
どうもドキュメントが間違っていて正しくはfirebase.firestore.FieldPathを使用する必要がある様です。
query.where(new firebase.firestore.FieldPath("dot.test"), "==", true)入れ子やmapの場合
query.where(new firebase.firestore.FieldPath("map", "dot.test"), "==", true)参考:
firebase - Firestore Query Properties with special characters - Stack Overflowこれで結果が返ってきました?
ちなみに「どんなときにフィールド名に記号とか保存するんだよw」と思われる方もいるかもしれませんので使用例をリンクしておきます^^
https://blog.knightso.co.jp/entry/2020/01/16/042702