20200116のJavaScriptに関する記事は24件です。

あれやこれやしてても、たった3時間ぐらいで簡単なWEBアプリを作ってUnunoで公開した話

簡単なwebアプリを1から作って、3時間ぐらいで完全無料ホスティングUnuboで公開までたどりついた話をします。

お名前で取得した独自ドメインを設定して、HTML/CSS/JavaScriptを使ったスタティックな簡単なwebアプリを公開しました。

Unuboとは

スクリーンショット 2020-01-16 13.46.57.png

■ Unubo - Cloud deployments made easy

Unuboとは、Webアプリ、DB、ブログを完全無料でホスティングできるサービスです。完全無料のホスティングを探していて、ここあたりの情報がでてきました。

■ Node.js,Python,RubyなどのWebアプリを完全無料でホスティング可能な「Unubo」を使ってみた!
■ Unuboを使ってみた話

Unuboっていうのがあるんだ〜と知ったのが、この記事を書き始めてから、昼飯挟んで約5時間ぐらい前のことです(笑)

実際に使った感想は、めちゃくちゃ簡単!しかも無料!です。

やったこと

やったことは、webアプリ作成、Unuboでの公開設定と独自ドメイン設定です。

  • webアプリ作成とUnuboでの公開設定
  1. Unuboにサインイン
  2. webアプリをGitHubにPUSH
  3. GitHubとUnuboを連携
  4. デプロイ、そして公開!

Unuboは、webアプリをGithubにPUSHすると自動的にデプロイしてくれます!

  • 独自ドメイン設定
  1. お名前でドメイン取得
  2. お名前でDNS設定
  3. Unuboにドメインを設定

webアプリをGitHubにPUSH

GitHubの使い方は、ご存知かと思いますが、以下のサイトがわかりやすかったです。

今さら聞けない!GitHubの使い方【超初心者向け】

独自ドメイン設定方法

自身のアプリ管理画面のDomainsというところに独自ドメイン設定方法が書かれています。ドキュメント見てもここに書いてあるって書いてました。
スクリーンショット 2020-01-16 18.33.22.png

独自ドメインを取得した会社のDNSレコードに、Unuboの指定するアドレス登録した後、Unuboに独自ドメインを追加します。

まずは、お名前の管理画面から取得した独自ドメインのDNS設定をしました。

  1. DNS設定/転送設定を開きます
  2. DNSレコード設定を利用するの設定するボタンを押します。

スクリーンショット 2020-01-16 18.34.52.png

$ dig asia.unubo.app
asia.unubo.app. 34.87.61.225

CNAMEを使う場合は、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

スクリーンショット 2020-01-16 18.35.21.png

DNSレコード設定用ネームサーバー変更確認にチェック入れて、確認画面へ進んで、設定完了。

※既存ドメインでは、設定によって既存サイトがみれなくなってしまったりもあるので、自己責任のもと、きちんと確認して設定ください。

独自ドメインがインターネットに反映されるまで待ちます。

反映されたであろう、しばらくしてから、UnuboのDomainsの②から無料SSLと一緒に独自ドメインを追加します。

スクリーンショット 2020-01-16 18.33.30.png

独自ドメインのDNS設定後、すぐにこれを設定するも、反映されていなかったのか一向に待っても無理でした。

独自ドメインのDNS設定したら、しばらく時間をあけて、この設定をしましょう!

設定終わってほっておくと、いとも簡単にUnubo上で独自ドメインでサイト開設!となりました。Unubo、ほんとちょっとしたwebアプリをすぐ公開したいときには、ほんと便利ですね!

ちなみに公開したWebアプリは?というと、これです。
TATEYOMITSUKURU 縦読み解読・作成アシストツール

ありがとうございました!

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

あれやこれやしてても、たった3時間ぐらいで簡単なWEBアプリを作ってUnuboで公開した話

簡単なwebアプリを1から作って、3時間ぐらいで完全無料ホスティングUnuboで公開までたどりついた話をします。

お名前で取得した独自ドメインを設定して、HTML/CSS/JavaScriptを使ったスタティックな簡単なwebアプリを公開しました。

Unuboとは

スクリーンショット 2020-01-16 13.46.57.png

■ Unubo - Cloud deployments made easy

Unuboとは、Webアプリ、DB、ブログを完全無料でホスティングできるサービスです。更にクレカ登録不要です。(2019/1現在)完全無料のホスティングを探していて、ここあたりの情報がでてきました。

■ Node.js,Python,RubyなどのWebアプリを完全無料でホスティング可能な「Unubo」を使ってみた!
■ Unuboを使ってみた話

Unuboっていうのがあるんだ〜と知ったのが、この記事を書き始めてから、昼飯挟んで約5時間ぐらい前のことです(笑)

実際に使った感想は、めちゃくちゃ簡単!しかも無料!です。

やったこと

やったことは、webアプリ作成、Unuboでの公開設定と独自ドメイン設定です。

  • webアプリ作成とUnuboでの公開設定
  1. Unuboにサインイン
  2. webアプリをGitHubにPUSH
  3. GitHubとUnuboを連携
  4. デプロイ、そして公開!

Unuboは、webアプリをGithubにPUSHすると自動的にデプロイしてくれます!

  • 独自ドメイン設定
  1. お名前でドメイン取得
  2. お名前でDNS設定
  3. Unuboにドメインを設定

webアプリをGitHubにPUSH

GitHubの使い方は、ご存知かと思いますが、以下のサイトがわかりやすかったです。

今さら聞けない!GitHubの使い方【超初心者向け】

独自ドメイン設定方法

自身のアプリ管理画面のDomainsというところに独自ドメイン設定方法が書かれています。ドキュメント見てもここに書いてあるって書いてました。
スクリーンショット 2020-01-16 18.33.22.png

独自ドメインを取得した会社のDNSレコードに、Unuboの指定するアドレス登録した後、Unuboに独自ドメインを追加します。

まずは、お名前の管理画面から取得した独自ドメインのDNS設定をしました。

  1. DNS設定/転送設定を開きます
  2. DNSレコード設定を利用するの設定するボタンを押します。

スクリーンショット 2020-01-16 18.34.52.png

$ dig asia.unubo.app
asia.unubo.app. 34.87.61.225

CNAMEを使う場合は、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

スクリーンショット 2020-01-16 18.35.21.png

DNSレコード設定用ネームサーバー変更確認にチェック入れて、確認画面へ進んで、設定完了。

※既存ドメインでは、設定によって既存サイトがみれなくなってしまったりもあるので、自己責任のもと、きちんと確認して設定ください。

独自ドメインがインターネットに反映されるまで待ちます。

反映されたであろう、しばらくしてから、UnuboのDomainsの②から無料SSLと一緒に独自ドメインを追加します。

スクリーンショット 2020-01-16 18.33.30.png

独自ドメインのDNS設定後、すぐにこれを設定するも、反映されていなかったのか一向に待っても無理でした。

独自ドメインのDNS設定したら、しばらく時間をあけて、この設定をしましょう!

設定終わってほっておくと、いとも簡単にUnubo上で独自ドメインでサイト開設!となりました。Unubo、ほんとちょっとしたwebアプリをすぐ公開したいときには、ほんと便利ですね!

ちなみに公開したWebアプリは?というと、これです。
TATEYOMITSUKURU 縦読み解読・作成アシストツール

ありがとうございました!

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

[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

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

Swipe.js スライド時にイベントを発火させる

Swipe.jsでiphoneライクなTimepickerを実装したので共有します。
出来上がりイメージ図はスクリーンショット 2020-01-16 19.50.25.png
こんな感じです。
コードはこちらから確認できます。
Timepicker using Swiper.js

公式ドキュメントはあまり参考ならなかったです。(わたし的に)
Swiper

補足

iphoneライクに指でスワイプできるようにすると
どうしてもイベントが発火しなかったので、
泣く泣く指スワイプは外しました。
(実装に成功した方は是非教えていただきたい)

ちなみに指スワイプを実装したコードは以下になります。

index.js
var 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
};

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

スペース派必見!絶対にインデントにはタブを使ってほしい理由

だ・である調チャレンジ!!

ソースを見やすくする工夫はいくつかある。
そのうちの1つが「インデント」だ。
このインデントについて説明する。
また、この記事でいうタブとは「ハードタブ」で、次のラインに揃えるものである。

インデントとは?

JavaScript
 let a = Math.random();
 if(a < 0.5){
    console.log("");
 }else{
    console.log("");
 }

クソコードさはどうでもいい。
とりあえず、このサンプルコードでは、見やすくするためにインデントを増やしている。
if文の{}の中が右に来ているだろう。それがインデントだ。

もしこのインデントがなかったらどうなるだろう。

JavaScript
 let 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#入門」をみてふと感じた。
設定で変更することもできるが、デフォルト時から少なくしていても、ほとんどの人は損をしないのに...

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

スペース派必見!絶対にインデントにはタブを使ってほしい理由まとめ

だ・である調チャレンジ!!

ソースを見やすくする工夫はいくつかある。
そのうちの1つが「インデント」だ。
このインデントについて説明する。
また、この記事でいうタブとは「ハードタブ」で、次のラインに揃えるものである。

インデントとは?

JavaScript
 let a = Math.random();
 if(a < 0.5){
    console.log("");
 }else{
    console.log("");
 }

クソコードさはどうでもいい。
とりあえず、このサンプルコードでは、見やすくするためにインデントを増やしている。
if文の{}の中が右に来ているだろう。それがインデントだ。

もしこのインデントがなかったらどうなるだろう。

JavaScript
 let 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#入門」をみてふと感じた。
設定で変更することもできるが、デフォルト時から少なくしていても、ほとんどの人は損をしないのに...

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

初心者 アプリ開発

初めましてプログラミング初心者です
お店の空席確認ができるアプリを作成しています
ログインして席の番号をクリック
jsで席の番号の色が変わるように実装しました

ログアウトの機能も実装してあるのですが

リロードした時に番号の色が変わらずログアウトした時のみ色が変わるような実装がしたいです

セッションを使えば実装できるのでしょうか

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

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

100日チャレンジの212日目

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

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

212日目は

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

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
}));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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に変換されるので親要素に対して適用すべし」とのことなので,適用しました.
いまのところうまくいっている様子です.

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

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);

やっていることは単純です。

  1. エンジンを生成
  2. 3Dシーンを生成
  3. シーンにカメラや照明、球体などを追加
  4. 描画ループ

という四段階でブラウザ 3D シーンの描画や操作までが可能になっています。シンプルでいいですね。

このサンプルは doc.babylonjs.com に書かれているものを少し改変したものとなっています。


また、 babylon.js は Playground を用意しているので、 CodePen などを使わなくてもより簡単に babylon.js を体験することが可能です。

createScene という関数を定義することで、面倒な HTML やリサイズ処理などを省略して書くことが出来ます。

サンプルとして 例えばこちら があります。読み込みに少し時間がかかりますが、物理演算を利用した素晴らしいデモが体験できます。

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

初心者による DOM イベントを使った処理-2

イベントリスナー/ハンドラーの削除

イベントリスナー/イベントハンドラーは設定することしかできなかったが、削除もできる。

イベントハンドラーの削除

イベントハンドラーはイベントリスナーよりも簡単に削除できる。これは、onXXXXメソッドに対してnull値を代入するだけである。

sample.html
<form>
 <input id="btn" type="button" value="ダイアログ表示" />
</form>
sample.js
window.addEventListener('DOMContentLoaded', function() {
 let btn = document.getElementById('btn')

 //イベントハンドラーを登録
 btn.onCLick = function() {
  window.alert('Hello!, Javascript!')
 }
 //イベントハンドラーを破棄
 btn.onClick = null
})

イベントリスナーの削除

イベントリスナーの削除には、removeEventListener()メソッドを使う。

sample.js
elem.removeEventListener(type, listener, capture)
 //elem : 要素オブジェクト type : イベントの種類 
 //listener : 削除するイベントリスナー capture : イベントの伝搬方向

これを使って書き直すと、

saple.html
<form>
 <input id="btn" type="button" value="ダイアログ表示" />
</form>
sample.js
document.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.js
document.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.js
document.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.js
document.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.js
document.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に設定されていると、「イベントの発生元を基準として、上位ノードへ向かって順にイベントリスナーが実行される」ので、上記の子どを実行した場合。

  1. #innerリスナーが発生しました
  2. #innerリスナー2が発生しました
  3. #outerリスナーが発生しました
  4. リンクによってページ移動

逆にcaptureがtrueに設定されていると、「上位ノードからイベントの発生元に向かって、イベントリスナーが実行される」ので、

  1. #outerリスナーが発生しました
  2. #innerリスナーが発生しました
  3. #innerリスナー2が発生しました
  4. リンクによりページ移動

となる。

イベントの伝搬をキャンセルする

たとえば、目的要素のイベントリスナーだけを実行して、上位ノードのイベントリスナーをキャンセルしたい場合があります。
このような場合に、stopPropagationメソッドを使います。

sample.js
document.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)

これによって、

  1. #innerリスナーが発生しました
  2. #innerりすなリスナー2が発生しました
  3. #リンクによってページ移動

outerイベントリスナーが実行されなくなりました。

イベントの伝搬を直ちにキャンセルする。

stopPropagationメソッドが上位/下位要素への伝搬がキャンセルされるのに対して、stopImmediatePropagationメソッドは、その場でイベントの伝搬をキャンセルします。

sample.js
document.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.js
document.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.js
func.bind(that, [引数])
 //func : 関数オブジェクト that : 関数の中でthisキーワードが示すもの

たとえば、

sample.js
document.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.js
document.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.js
document.addEventListener('DOMContentLoaded', function() {
 let data = {
  title: 'javascript',
  price: 2860,
 }

 document.getElementById('btn').addEventListener('click', () => {
  console.log(this.title + '/' + this.price)
 }, false)
})

アロー関数では、thisはアロー関数自身が宣言された場所によって決まります。

参考資料

山田祥寛様 「javascript本格入門」

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

Next.jsでFirebase analyticsを使用する場合の注意

概要

Next.jsにanalyticsを導入時に起きた問題を投稿します。

現象

Next.jsなどでanalyticsを使用する際にサーバー側でエラーが発生する。

理由

analyticsの内部でwindowが呼ばれていたため

解決法

if (process.browser) {
    firebase.analytics().logEvent(<イベント名>);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vsCodeにgruntを導入してみた

概要

各種jsのcombine(結合)及びminify(難読)を行うためのツールで他にも有名どころとしてgulp、google closure compilerがある。
visualStudioCodeで作業することが多かったため、vsCodeへの導入をメモ

導入手順

※導入するプロジェクトファイあるのルートを開いた状態でスタートすること

  1. Node.jsをインストールする
    (vsCodeでnpm, nodeが使用できるようになる)
    (Node.js)https://nodejs.org/en/
     LTSの方をインストール

  2. Node.jsがインストールできたら、vsCodeを再起動する

  3. vsCodeのターミナルでgrunt-cliをインストール
    npm install -g grunt-cli

  4. フォルダにgruntをインストール
    (初回のみ gitにコミット済みであればスキップ)
    場所は、プロジェクトのルートがいい(srcと同じ階層など)
    npm install grunt -save-dev
    grunt -Vでverが確認できればおk

  5. Gruntfile.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

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

初学者は教材選びが大事

前回のあらすじ

・新人プログラマ向けの本が分かりづらすぎて学習効率が下がってた

今日やったこと

・if文とswitch文を使って、コンピューター様とじゃんけんするプログラムを作った
(今回の教科書『いちばんやさしいJavascriptの教本』)
(学習環境 エディタ→メモ帳 ブラウザ→EDGE)

困ったこと

・エラーが2箇所出て、場所の特定に難航した
特定方法→ブラウザのコンソールツールを見つつ、前後4行くらいを教本と見比べていく
・原因その1
===記号のつけ忘れ
・原因その2
教本の誤植(プラス記号の抜け落ち)
本のHPを見て判明

感想

古めの版年に出た本を使って学習すると、誤植に振り回されるなぁと思った。
ランダムに数を生成するメソッド「Math.random()」は応用が利きそうだなァと思った。
エラーがでたとき、落ち着いて対処できるようになりたいと感じた。

次回の目標

・関数について再学習しよう

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

画像読み込みマネージャーのようなものを作ってみた

概要

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();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】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の勉強をがんばります。

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

画像をアップロード前に圧縮する流行りの方法【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

スクリーンショット 2020-01-16 13.32.29.png

スクリーンショット 2020-01-16 14.17.23.png

(最大 100x100 px にしているので2kbくらい)

処理して簡単に解説する

Canvasでごりっと処理するとか、ライブラリに頼るとか方法はあると思いますが今回は compressorjs を紹介したいっす。

component.js
import 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)

実際の処理はこんな感じで横幅・縦幅の最大とか圧縮率とかたくさんの オプション があります。また成功時と失敗時は successerror のコールバックに書くみたいです。

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 とかで定義元とか飛んで行って自分で触ってみてください!!

スクリーンショット 2020-01-16 14.09.43.png

終わりに

ブラウザのAPIを叩くときにTypeScriptすごく開発体験良かったです。MDNを見に行かなくても何が返ってくるかわかるので、null制御とかそういうのがやりやすかったです。コード書いてて楽しかった。

なにか間違い見つけたら編集リクエストください!!!

最後まで読んでいただきありがとうございます。!!

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

とりあえずreact-hook-form

はじめに

react系のフォームバリデーションライブラリreact-hook-formのざっくり使い方です。

公式のドキュメントでは、

  • 超軽量なパッケージ
  • 再レンダリングを最小に押さえて、マウントの高速化
  • フォームの値がローカル管理される為、他パッケージに依存しない

等々の利点が挙げられている。

formikとの比較

download

スクリーンショット 2020-01-16 0.49.24.png
https://www.npmtrends.com/redux-form-vs-formik-vs-react-hook-form

size

formik

スクリーンショット 2020-01-16 0.54.11.png

react-hook-form

スクリーンショット 2020-01-16 0.54.30.png

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))

form1.gif

Form1
import 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を使用。

form2.gif

Form2
import 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をリセットできる関数。
リセット時に値を渡すとデフォルトの値としてリセットできる。
form3.gif

Form3
import 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

値を動的に設定できる。

form4.gif

Form4
import 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

おわりです。込み入ったことは試していないのですが、記述が簡単な印象でした。

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

【PHP】stripe API で定額課金を実装する

はじめに stripeとは

stripeは、クレジットカードなどの決算処理を代行してくれるサービスです。
シンプルな実装で決済処理を実現でき、カード情報を自社サーバで持たなくても良いなどの特徴があります。

大まかな流れとして、

  1. サービスの請求モデルを作成
  2. フロントサイドでカード情報を取得
  3. バックエンドで顧客登録、決済処理

と進んでいきます。

サービスの請求モデルを作成する

1. stripeに登録する

1-1.はじめに、stripeの公式ページにて、今すぐ始めるをクリック。

スクリーンショット 2020-01-15 20.24.58.png

1-2メールアドレス、名前、パスワードを入力登録。

なお、テスト用に登録する際には、メールアドレスの存在確認は求められません。
この時点で、登録が完了いたしました。
スクリーンショット 2020-01-15 20.27.48.png

1-3.ダッシュボードが表示される

登録が完了したら、いくつかの質問がされるので答えていきます。
質問が完了したら、次のようなダッシュボードが表示されます。
スクリーンショット 2020-01-15 20.34.07.png

2.テストAPIキーを取得する

ダッシュボードが表示されたら、まずはAPIキーを取得してみましょう。
今回はテスト用のAPIキーを取得します。
ダッシュボードのテストAPIキーの取得をクリックしてください。スクリーンショット 2020-01-15 20.39.48.png

公開可能キーシークレットキーの2つが表示されています。
スクリーンショット 2020-01-15 20.43.27.png

公開可能キー

公開可能キーは、不特定多数に表示されてもかまわないキーです。
JavaScriptなど、フロントサイドで使用されるキーです。

シークレットキー

一方シークレットキーは秘密にしなければなりません。
PHPなど、サーバーサイドで使用されます。

3.商品(Product)を登録する

3-1.Billing->商品をクリック

はじめに、提供するサービスである、商品(Product)を登録します。
画面左側のBillingをクリックしてから、商品が表示されるのでもう一度クリックしてください。

スクリーンショット 2020-01-15 21.24.24.png

テスト商品を追加をクリックします。
スクリーンショット 2020-01-15 21.31.18.png

3-2.商品を作成

商品名を入力して、商品を作成をクリックします。
スクリーンショット 2020-01-15 21.33.31.png

なお、ここでは商品の価格などの情報は入力しません。
商品の配下に、プラン(Plan)を作成して個別に料金体系(価格、通貨、支払い間隔など)を設定します。
一つの商品に対して、複数のプランが設定できます。
例えば、有料会員という製品には以下のプランが紐付けられていると考えられます。

  • 有料会員
    • ブロンズ会員 月額500円
    • シルバー会員 月額1,000円
    • ゴールド会員 月額3,000円

4.プランを作成する

4-1.プランの作成

それでは実際にプランを作成してみます。
商品を作成したら、そのままプランを作成するページへ遷移します。
スクリーンショット 2020-01-15 21.41.54.png
スクリーンショット 2020-01-15 21.43.12.png

必要な項目を入力して、料金プランを追加をクリックすると、プランが追加されます。

4-2.プランIDの取得

後ほど会員をプランに追加する際に、プランIDが必要になるのでここで取得します。
prod_から始まる商品IDではないことに注意してください
先程作成したプランクリックして詳細を表示します。
スクリーンショット 2020-01-16 0.17.41.png

plan_から始まる英数字がプランIDです。
スクリーンショット 2020-01-15 22.25.39.png

コードを実装する。

5.ライブラリをインストールする

いよいよコードの実装にはいります。
はじめに、Composerを利用して、PHPのライブラリを入手します。

Composerのインストール

composer require stripe/stripe-php

6.フロントサイドの実装(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>

下記画像のように、カード番号・有効期限・確認番号・郵便番号を入力するフォームが生成されました。
スクリーンショット 2020-01-16 11.09.10.png

これらの項目はすべて必須項目です。
もし、郵便番号の入力をなくしたいなら、入力フォーム生成時にhidePostalCodetrueにします。

    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.登録を確認する

ダッシュボードに戻り、左側の顧客をクリックすると、先程登録した顧客情報が表示されているかと思います。
スクリーンショット 2020-01-15 23.34.23.png

無事顧客が登録されていましたね。
さらに、詳細情報をみると顧客の定期支払を確認することができます。
スクリーンショット 2020-01-15 23.33.40.png

トライアル期間を設定したので、請求書をみると初回の支払いは0円で、次回請求日は3ヶ月後に設定されていることが確認できます。(今回登録した日付は01/15)
スクリーンショット 2020-01-15 23.34.58.png

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_endtrueにしてプランをSubscription::updateで更新することで、解約後も有効期限内まではプランが有効になります。

もし、解約後はすぐにプランを終了したいときには、以下のようなコードを書きます。

// 解約後はすぐにプランが終了する。
$subscription = \Stripe\Subscription::retrieve('sub_49ty4767H20z6a');
$subscription->cancel();

8-2.解約を確認

ダッシュボードを確認すると、先程の会員が有効期限である04/15にプランがキャンセルされることが確認できました。
スクリーンショット 2020-01-16 0.06.57.png

おわりに

定額課金の決済の簡単な流れを説明しました。
他にも期間中のアップグレード、ダウングレードや途中解約に対する比例配分、更新時にカードが利用できなかった場合など複雑な状況にも対応できます。

ぜひ、ドキュメントも参照してみてください。

参考サイト

公式ドキュメント
API リファレンス
Stripe Billing 101
Stripe.js & Elements を利用して決済フローを理解する

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

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で挙動が変わることがあるという点は注意すべきところですね。

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

[Vue.js] ケバブケースとかキャメルケースとかパスカルケースとか

Vueでのケースの書き方って場所によって何がいいか若干悩みますよね。(私だけ?)
なので、まとめてみました。

そもそも○○○ケースって?(復習)

一言で言うと「クラス名や変数名等の名前の付け方の総称」
覚えておくべきケースをざっと説明すると。。

ケバブケース

文字と文字の区切りを-で表現するやり方。「チェインケース」とも言う。
ケバブのお肉をぶっさしている感じからきているとか。:meat_on_bone:

this-case-is-god

キャメルケース

文字と文字の区切りを大文字で表現するやり方。
よく見るやつです。ラクダのこぶですね。:camel:

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

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

npm を使用して、誰かが書いたコードをリユースする方法

npmで誰かが書いたコードを使用する

NPM(Node Package Manager)は、誰かが書いたコードを使用できる便利な管理システム。

自分で1から作るんじゃなくて、誰かが書いたコードを使用できるなら、それをリユースして無駄な時間を削減しようっていう考え方を元に作成された管理システム。

https://www.npmjs.com/

npm を使用するまでの手順

今回は intro-to-node のフォルダ内のindex.jsというファイルでnpmを使用できるようにします。

スクリーンショット 2020-01-14 15.14.42.png

npm を初期化する

コマンドラインでちゃんと intro-to-node フォルダに移動しているか確認してください。

スクリーンショット 2020-01-14 15.17.05.png

確認できたら

npm init

で初期化します。

色々入力を求められるので、こんな感じで入力していきます。

スクリーンショット 2020-01-14 15.20.32.png

まぁほとんどコマンドラインの提案通りにEnter押していくだけで、実際入力したのは、description(説明)とauthor(著者)のみ。

すると、package.jsonファイルが intro-to-nodeフォルダに作成されているのがわかります。

スクリーンショット 2020-01-14 15.22.19.png

ちなみに、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』 と検索したら、出てきました。

使用するパッケージをインストールする

各パッケージには、インストールの部分があるので、コマンドラインを使用してインストールします。

スクリーンショット 2020-01-16 08.34.24.png

ちゃんと自分が使用するフォルダ(intro-to-node)にいるか確認してから、インストールを実行しましょう。

スクリーンショット 2020-01-16 08.37.29.png

『npm install パッケージ名』

これでインストールは完了。

パッケージを使用する

使用するときは、パッケージのUsageを見るとわかりやすいです。

スクリーンショット 2020-01-16 08.40.59.png

index.js
// superheroes をファイル内で使用できるようにする
const superheroes = require("superheroes");


// Usage に記載されている random() を使用する
var hero = superheroes.random();

// ランダムで Super Hero の名前を取得できる

これにてnpmの使用方法まとめ終了です。

このコンテンツはUdemyの The Complete 2020 Web Development Bootcamp を参考にしています。

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

【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);

保存されたデータ↓

スクリーンショット 2020-01-16 5.00.13.png

これらのフィールドを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());
});

結果↓
スクリーンショット 2020-01-16 5.14.29.png

入れ子の場合はドット区切りで指定

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

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