20210329のJavaScriptに関する記事は28件です。

if文〜JS〜

今回は関数とif文について学習していきます!!
まず、関数の前にif文について学習していきます。

if文について

if文とは条件分岐のときに使います。

main.js
if (条件式){
 trueだったときの処理
} else if (条件式){
 falseだったときの処理
} else {
 どちらにも当てはまんなかったとき
}

上記のように流れていきます。

では、実際にプログラムを書いていきましょう。

まず、変数か定数を定義します。
Jsでは、変数はlet,定数はconstを宣言します。前の記事でも学習しましたが、変数は入れ替えができますが定数は変更することができないので注意しましょう、1つのプログラムを書く中では変数ではなく定数を極力使うようにしましょう!!

main.js
const score = 10;

    if (score >= 80) {
      document.write("よくできました");
    } else if (score >= 60) {
      document.write("半分取れました!!更に頑張りましょう!!");
    } else {
      document.write("頑張れよ");
    }

定数constに10点を代入します。基準10点になります。
ここで、if文で条件式をかきます。
①scoreが80点以上だったら
②scoreが60点以上だったら
③全てに当てはまらなかった場合
の条件式をかきます。

console.logではなく、今回はdocument.writeでブラウザに表示するようにしました。
ただ、この、if分でもいいのですが、長いので省略しちゃいましょう?

main.js
 score >= 80 ? document.write("よくできました") : document.write("半分取れました!!更に頑張りましょう!!") 

となります!!

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

結局Webpackってなんのためにあるの?

はじめに

JavaScript(フロントエンド)を始めてぶち当たるのがWebpack, Babelだと思います。(僕はぶち当たりました)
この記事ではWebpackの使い方ではなく、「Webpackって結局なんのためにあって、あるとなんで嬉しいのか」についてを書きます。
もし、間違っている、追記すべきことがあれば編集リクエストなどで教えていただけると幸いです。

Webpackの役割

Webpackは下の記事にもある通り、モジュールバンドラーと呼ばれるものです。

Webpackについて全く知らないよって人はこの記事を読んでみると何をするものかはイメージがつくと思います。

簡単に説明すると、Webpackは複数のJavaScriptファイルを1つにまとめてくれるものです。(JavaScriptファイル以外にもCSSとか画像ファイルもできます)

複数のscriptタグじゃだめなの?

HTMLには複数のscriptタグを埋め込むことができ、複数のJavaScriptファイルを読み込むことができます。

例えば、以下のようなコードがあるとします。

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Sample Page</title>
  </head>
  <body>
    <script src="greets.js"></script>
    <script src="output.js"></script>
  </body>
</html>
greets.js
const japanese = 'こんにちは';
output.js
console.log(japanese);

この場合、ブラウザのコンソールにはこんにちはと出力されます。
greets.jsが先に読み込まれ、その後にoutput.jsが読み込まれており、output.js内のjapanese変数にはこんにちはが入っているためです。

しかし、index.htmlが以下のような場合はどうでしょうか。

index.html(変更後)
<!DOCTYPE html>
<html>
  <head>
    <title>Sample Page</title>
  </head>
  <body>
    <script src="output.js"></script>
    <script src="greets.js"></script>
  </body>
</html>

output.jsgreets.jsの順番が逆になっています。
この場合、outputs.jsjapanese変数はまだ定義されていないため、コンソールにundefinedが出力されます。

次に、それぞれのファイルで同じ変数名を定義した場合を見てみましょう。

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Sample Page</title>
  </head>
  <body>
    <script src="greets1.js"></script>
    <script src="greets2.js"></script>
    <script src="output.js"></script>
  </body>
</html>
greets1.js
var japanese = 'こんにちは';
greets2.js
var japanese = 'Konnichiwa';
output.js
console.log(japanese);

この場合、コンソールにはKonnichiwaが出力されます。
これは、ブラウザにはネームスペースが1つしかないため、greets1.jsが読み込まれ、定義されたjapanesegreets2.jsが読み込まれたときに代入(上書き)されてしまうためです。(大人の事情により、constじゃなくてvarを使ってます。気になる方はJavaScriptのvarの性質を調べてみると、あ〜となります。)

なにが言いたいのかというと、複数のJavaScriptファイルをロードする際には、読み込む順番や同じ変数名を使っていないかを確認する必要があるということです。
これを数十、数百ファイルで行うのは人間には不可能だと思います。

そこで出てくるのがWebpackです。

Webpackを使うとどうなるの?

Webpackを使うことで、複数のscriptタグでJavaScriptファイルをロードするときに気をつけなければならないことを気にする必要がなくなります。

実際にWebpackを使った場合を見てみましょう。

greets.js
const japanese = 'こんにちは';
export default japanese;
index.js
import japanese from './greets';
console.log(japanese);

簡単に説明すると、index.jsgreets.jsjapanese変数をインポートして、それをコンソールに出力しています。
このgreets.jsindex.jsWebpackで1つのファイルにまとめるとこのようになります。

main.js
(()=>{"use strict";console.log("こんにちは")})();

見やすくすると、

main.js(見やすくしたもの)
(() => {
  "use strict";
  console.log("こんにちは")
})();

console.log("こんにちは")となっていることがわかります。
Webpackがimport / exportを解析し、必要なもの(今回の場合はjapanese)を見つけ出し、コードを生成してくれているおかげで、JavaScriptファイルを読み込む順番を考える必要がなくなりました。

違うファイルで同じ変数名がある場合はどうでしょうか。

牛丼のテイクアウトの料金を出すtakeout.jsとイートインの料金を出すeatin.jsを例にしたコードを考えます。takeout.jseatin.jsのコードは以下のようになりました。

takeout.js
const gyudon = 350;
const tax = 1.08

export default gyudon * tax;
eatin.js
const gyudon = 350;
const tax = 1.1

export default gyudon * tax;
index.js
import takeout from './takeout';
import eatin from './eatin';

console.log('takeout', takeout);
console.log('eatin', eatin);

scriptタグで読み込んだ場合、変数名(gyudon, tax)が同じなため、上書き or エラーになりますが、Webpackだとどうでしょうか。
実際に、Webpackでまとめたコードは以下です。

main.js
(()=>{"use strict";console.log("takeout",378),console.log("eatin",385.00000000000006)})();

例のごとく、見やすくします。

main.js(見やすくしたもの)
(() => {
  "use strict";
  console.log("takeout", 378), console.log("eatin", 385.00000000000006)
})();

ちゃんとテイクアウト、イートインでの料金が計算されていることがわかります。
これは、Webpackがファイルごとにネームスペースを区切っているため、異なるファイルで同じ変数名を使っても混ざることなくコードが生成されます。

気をつけなければならないことがなくなったので、複数のscriptタグを使ってJavaScriptファイルをロードするときに比べ、開発がしやすくなったのではないでしょうか。

まとめ

Webpackを使う前は、ブラウザのネームスペースが1つしかなく、複数のscriptタグを使うとロードの順序や変数名を気にして開発しなければなりませんでした。しかし、Webpackを使うことによってネームスペースの問題や、ロードの順序を気にする必要がなくなり、開発がしやすくなります。
規模の小さい場合だと、恩恵をあまり受けることができないかもしれませんが、規模が大きくなってくると確実にWebpackの恩恵を受けることができると思います。

今回はWebpackのお話でしたが、モジュールバンドラーにはRollupなどもあるので、そちらも見てみると良いかもしれません。
また、WebpackはJavaScriptだけでなく、CSSや画像などもまとめることができるので、それに挑戦してみたりすると、よりWebpackについて知れると思います。

おまけ

最近のブラウザだと以下のようにモジュールに対応しているものがあります。
(「ESModule ブラウザ」とかで調べると出てきます)

image.png

実際にコードを書くと以下のようになります。

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Sample Page</title>
  </head>
  <body>
    <script src="index.js" type="module"></script>
  </body>
</html>
index.js
import japanese from './greets.js';
console.log(japanese);
greets.js
const japanese = 'こんにちは';
export default japanese;

ただ、現段階ではWebpackなどのモジュールバンドラーを使ってバンドルするのが無難だと僕は思います。

リンク

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

konva.js で地図を描く (2)

前回の続き
次はkonvaのいろんな図形を使って、簡易的な地図を描いていきます

グループの生成

画像や図形たちをグループ化します

sample.js
var group = new Konva.Group({
    x: 10,
    y: 10
});

作った要素をこのグループに追加していく

軌跡の描画

line のオブジェクトを使って描画
lineは[x0, y0, x1, y1, x2, y2 .....] という座標を指定して、そのポイントを結ぶ線を描きます

convasの座標は
左上が(0,0)右下が(200, 200) (例:canvasのサイズが200pxの時)
なので、左上を始点にして

  • x(正の方向)→ 右側にlineが描かれる (負の時は逆)
  • y(正の方向)→ 下側にlineが描かれる (負の時は逆)

lineを描く例
今回は各lineのポイントごとにクリックイベントなど拾えるように、各区間ごとに線を描いていきます

sample.js
// [x,y]の座標が詰まった配列
var table = [[3,10],[5,20],[10,50],[30,60],[50,80],[80,100],[100,100],[110,120],[120,150],[150,170]];

for (let i = 1; i < table.length; i++) {
    var prevX = table[i-1][0];
    var prevY = table[i-1][1];
    var currentX = table[i][0];
    var currentY = table[i][1];
    console.log(i, px, py, cx, cy);

    var line = new Konva.Line({
        points: [prevX, prevY, currentX, currentY],
        stroke: '#696969',
        strokeWidth: 3,
        lineCap: 'round',
        lineJoin: 'round',
        id: `line_${i}`  //lineごとにidをふれる
    })
    line.on('mouseup', (e) => {
        console.log(e.currentTarget.attrs.id); //クリックした時にid取得できる
    });
        group.add(line);
    }

スタート地点とゴール地点を用意

今回はスタート地点を○、ゴール地点を☆で生成

sample.js
let start = new Konva.Circle({
    x: table[0][0], //配置場所
    y: table[0][1], //配置場所
    radius: 10,
    fill: 'red',
    stroke: 'black'
});
group.add(start) //作った要素はgroupに追加


let goal = new Konva.Star({
    x: table[table.length-1][0], //配置場所
    y: table[table.length-1][1], //配置場所
    numPoints: 5,
    innerRadius: 7,
    outerRadius: 15,
    fill: 'yellow',
    stroke: 'black'
});
group.add(goal) //作った要素はgroupに追加

最後描画する

sample.js
  layer.add(group); // 最後groupをlayerにaddする
  stage.add(layer); // layerをstageにaddする
  layer.draw();     // 描画

結果

こんな感じのmapができる
さらにアニメーションをつけたり、各要素にクリックイベントつけたりなど、簡単にできるので便利でした!
map.png

注意点

groupにaddする時は、addする順番で描画されるため、順番が重要(後から追加されたものが上にくる)
layerを分けて、zIndexを設定することで上にくる要素を変更できる

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

konva.js で地図を描く (1)

konva.jsとは

  • konva.js
    • デスクトップやモバイルアプリケーション向けの2Dキャンバスライブラリ
    • 図形や画像を描画したり、アニメーションしたり、イベントリスナーをつけたりなどが簡単にできる

今回はこのkonva.jsを使って簡易的な地図を描いた内容をまとめます

html側

samle.html
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="canvas-container"></div>
  </body>
</html>

html側はdivを用意するだけ

ライブラリ読み込み

スクリプトタグに以下埋め込み
<script src="https://unpkg.com/konva@7.0.3/konva.min.js"></script>

or

npm install konva

or

公式サイトからダウンロード

js側の実装

konva.jsの構造

公式サイトより引用

              Stage  <--- (1)divのidを取得してstageを用意
                |
         +------+------+
         |             |
       Layer         Layer  <--- (2)layerを用意
         |             |
   +-----+-----+     Shape  <--- (3)要素を用意。複数ある場合はグループ化できる
   |           |
 Group       Group
   |           |
   +       +---+---+
   |       |       |
Shape   Group    Shape
           |
           +
           |
         Shape
sample.js
// (1) divのidを取得してstageを用意
const stage = new Konva.Stage({
  container: 'canvas-container', //親要素のdivタグのidを指定
  width: 200, //キャンバスの横幅
  height: 200 //キャンバスの高さ
});

// (2)layerを用意
const layer = new Konva.Layer();

// (3)要素を用意。まずはマップの枠(四角)を用意
const box = new Konva.Rect({
  x: 0, //配置場所
  y: 0, //配置場所
  width: 200, //横幅
  height: 200, //高さ
  fill: "#ffffff", //塗り潰しの色
  stroke: "#000", //枠線の色
  strokeWidth: 1, //枠線の太さ
  opacity: 1, //透過率
  cornerRadius: [3, 3, 3, 3] //四角の角を丸める
});

layer.add(box); //作った四角をlayerにadd
stage.add(layer); //layerをstageにadd (階層の上に順番に追加していく)

layer.draw(); //これで描画


結果

まずこれで四角がcanvasに描かれる
(内部的にはこんな感じでdivの中にcanvasが生成されている)

result.html
<div id="canvas-container">
  <div class="konvajs-content" role="presentation" style="position: relative; user-select: none; width: 200px; height: 200px;">
  <canvas width="400" height="400" style="padding: 0px; margin: 0px; border: 0px; background: transparent; position: absolute; top: 0px; left: 0px; width: 200px; height: 200px; display: block;">
  </canvas>
  </div>
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jest mockClear(), mockReset(), mockRestore() の違い

Jest の mockClear(), mockReset(), mockRestore() の違いが分かりづらいのでまとめておく。
また jest.clearAllMocks(), jest.resetAllMocks(), jest.restoreAllMocks() もすべてのモックが対象になるだけで挙動としては同じ。

mockClear()

mockFn.mock.calls, mockFn.mock.instances を初期化する。

const now = () => Date.now();

describe('mock example', () => {
  const mockedDateNow = jest.spyOn(Date, 'now').mockReturnValue(0);

  it('mockClear', () => {
    now();
    mockedDateNow.mockClear();
    // calls, instances はすべて初期化されている
    expect(mockedDateNow.mock.calls.length).toBe(0);
    expect(mockedDateNow.mock.instances.length).toBe(0);
    // mockReturnValue の設定は初期化されていない
    expect(now()).toBe(0);
  });
});

mockReset()

mockFn.mock, mockFn.mock.calls, mockFn.mock.instances を初期化する。
また mockImplementation, mockReturnValue 等で設定した実装、戻り値もすべてリセットされる。

const now = () => Date.now();

describe('mock example', () => {
  const mockedDateNow = jest.spyOn(Date, 'now').mockReturnValue(0);

  it('mockReset', () => {
    now();
    mockedDateNow.mockReset();
    // calls, instances はすべて初期化されている
    expect(mockedDateNow.mock.calls.length).toBe(0);
    expect(mockedDateNow.mock.instances.length).toBe(0);
    // mockReturnValue の設定も初期化されている
    expect(now()).not.toBe(0);
  });
});

mockRestore()

jest.spyOn によって作成されたモックをオリジナルのものへ戻す。

const now = () => Date.now();

describe('mock example', () => {
  const mockedDateNow = jest.spyOn(Date, 'now').mockReturnValue(0);

  it('mockRestore', () => {
    now();
    mockedDateNow.mockRestore();
    // mockRestore() により Date.now がモックでなくなっている
    expect((Date.now as any).mock).toBe(undefined);
  });
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Deno Deployをやってみた

ツイッター見てたら流れてきたのでやってみた

Deno Deployとは?

Deno Deploy is a distributed system that runs JavaScript, TypeScript, and WebAssembly at the edge, worldwide. The service deeply integrates the V8 JavaScript runtime with a high performance asynchronous web server to provide optimal performance without unnecessary intermediate abstractions.
https://deno.com/deploy

google翻訳

Deno Deployは、 JavaScript、TypeScript、およびWebAssembly を世界中のエッジで実行する 分散システム です。このサービスは、 V8 JavaScriptランタイムを高性能非同期Webサーバーと緊密に 統合して、不要な中間抽象化なしで最適なパフォーマンスを提供します。

どうやら今日発表されたDeno用の公式ホスティングサービス(?)のようです!
githubに限らず、web上にあるJavaScriptとTypeScriptのコードからサーバーが建てられると。
公式ドキュメントはこちらに

まだベータ版のようですが、さっそく試しに使ってみたいと思います。

① プロジェクト名を決める

ここで入力した名前が初期ドメインになります。(後から変更可能)
image.png

② githubでリポジトリを作成

今回は試すだけなので、リクエストヘッダをそのままJSONで返すサーバーを建ててみたいと思います。
新しいリポジトリを作成して、公式ドキュメントを参考にコードを書きます。

image.png

③ githubのURLをコピペ

「githubからデプロイ」と「URLからデプロイ」の2種類あるみたいですが、ここではgithubからデプロイを選んで、URLをコピペしてきます。

image.png

③ githubと連携させる

githubアプリと連携しますか?みたいなダイアログが出てくるので、許可します。

image.png

④ デプロイ完了

デプロイしたプロジェクトには、「Visit」というリンクから飛べます。

image.png

ここまで10分弱ですが、無事デプロイできました!
こちらで公開中です。

image.png

料金は…?

こちらのページにある通り、ベータ期間中は無料で使用でき、ベータ期間が終わると、詳細な価格が発表されるとのこと。

 

最後に、公式ドキュメントからDeno Deployの特色を引用して終わります。(google翻訳)

  • Web上に構築:ブラウザーと同じように、fetch、WebSocket、またはURLを使用します
  • TypeScriptおよびJSXの組み込みサポート:タイプセーフコード、およびビルド手順なしの直感的なサーバー側レンダリング
  • Web互換のESモジュール:明示的なインストールを必要とせずに、ブラウザーと同じように依存関係をインポートします
  • GitHubの直接統合:ブランチにpushし、デプロイされたプレビューを確認し、margeして本番環境にリリースします
  • 非常に高速:1秒未満で展開し、ユーザーの近くでグローバルにサービスを提供
  • URLからデプロイ:URLだけでコードをデプロイします
  • CloudflareWorkers®と互換性のあるAPI:既存の大規模なエコシステムの恩恵を受けられます

https://deno.com/deploy/docs

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

Ajaxを簡単に実装するための覚え書き

Ajaxで何か送信するときのための覚え書き

var req = new XMLHttpRequest();
req.onreadystatechange = function() {
    if (req.readyState == 4) { // 通信の完了時
    if (req.status == 200) {
        // 通信の成功時
    }
    }else{
        // 通信中の処理
    }
}
req.open('POST', '送信先URL', true);
req.setRequestHeader('content-type','application/x-www-form-urlencoded;charset=UTF-8');
req.send('hoge=&example='); 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

通話機能 OverconstrainedError Deviceidの解決方法

はじめまして、プログラミング初心者のコージです。今回はデバイス選択通話機能を実装するうえで苦労したOverconstrainedError Deviceidの解決方法を忘備録として残しておきます。
苦労したとはいってもコードの改変はたった1行です笑

call.js
      let audioId = $('#audioSource').val();
      let videoId = $('#videoSource').val();
      let constraints = {
          audio: {deviceId: {exact: audioSource}}, <-ここの記述を変更
          video: {deviceId: {exact: videoSource}} 
call.js
      let audioId = $('#audioSource').val();
      let videoId = $('#videoSource').val();
      let constraints = {
        audio: {
            deviceId: audioId <-exactを削除
           },
        video: { 
            deviceId: videoId <-exactを削除
           }

まさかこれだけで3日苦しんだエラーが解決するとは、、、?
Chromeでこの形式で指定できるようになったのはごく最近らしいです。上が以前の指定形式らしい。
古いから制約に引っ掛かるのかな?

全てのコードを見たい方はgithubのapp->javascriptからどうぞ
ビデオ通話機能を実装したwebアプリケーションのコード: circlefriends

以上

twitter: @siron_www 日常のこともつぶやいてるので友達感覚でフォローしてください?

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

kintone上でWebSpeechAPIを利用して音声認識して文字起こしする【改良編】

本記事を読むにあたっての注意点

本記事は前回のkintone上でWebSpeechAPIを利用して音声認識して文字起こしする【動作確認編】で作ったものを実装するために改良した部分の紹介と、Web Speech APIの感想をまとめたものです。
まだ読んでいらっしゃらない方はそちらを先に読んでいただければ幸いです。

コード全文

※紹介しきれなかったコードも含まれます。

全文表示する
(function () {
    "use strict";
    kintone.events.on(["app.record.edit.show", "app.record.create.show"], function (event) {

        // start,stop ボタン生成
        const startButton = document.createElement("button");
        startButton.innerText = "録音開始";
        startButton.id = "start-btn";
        startButton.className = "recognition-buttons";

        const stopButton = document.createElement("button");
        stopButton.innerText = "録音終了";
        stopButton.id = "start-btn";
        stopButton.className = "recognition-buttons";

        // 文字起こし用<textarea>作成
        const tempForm = document.createElement("textarea");
        tempForm.id = "temp_input";
        //tempForm.type = "text";
        tempForm.cols = "150";
        tempForm.rows = "10";
        tempForm.innerText = "(録音開始ボタンをクリックで音声受付を開始します)";
        //tempForm.style.resize = "none";
        kintone.app.record.getSpaceElement('form_space').appendChild(tempForm);

        // // cautionフィールドにメッセージを追加
        const cautionElem = document.createElement("p");
        cautionElem.innerHTML = `・録音受付中はブラウザのタブに赤い丸ボタンが出現し点滅します。録音終了ボタン押下または一分放置すると録音が自動終了します。<br>
                                        ・録音終了後に句読点や改行を付けるなどの編集が可能です。<br>
                                        ・保存ボタンを押すと、音声受付フィールドの値が清書後フィールドに転記又は上書きされます。`
        cautionElem.id = "caution-p";
        kintone.app.record.getSpaceElement('caution').appendChild(cautionElem);

        // Web Speech API の設定 (SpeechRecognitionクラス)
        const SpeechRecognition = webkitSpeechRecognition || SpeechRecognition;
        const recognition = new SpeechRecognition();
        recognition.lang = "ja-JP";
        recognition.interimResults = true;
        recognition.continuous = true;

        let resultString = "";
        recognition.onresult = (event) => {
            let intermString = "";
            for (let i = event.resultIndex; i < event.results.length; i++) {
                let transcript = event.results[i][0].transcript;
                if (event.results[i].isFinal) {
                    resultString += transcript + " ";
                } else {
                    intermString = transcript;
                }
            }
            document.getElementById("temp_input").value = resultString + "<" + intermString;
        };

        // 録音ボタンの処理、ボタンの設置
        startButton.onclick = () => {
            // 録音開始時のみメッセージを表示
            tempForm.innerText = "(音声を受け付けています)";
            recognition.start();
        };
        stopButton.onclick = () => {
            recognition.stop();
        };
        kintone.app.record.getSpaceElement('buttonSpace').appendChild(startButton);
        kintone.app.record.getSpaceElement('buttonSpace').appendChild(stopButton);
        return event;
    });

    // 保存実行前に一時保存から取得してフィールドに移す。
    kintone.events.on("app.record.edit.submit", function (event) {
        // 音声入力しなければbreak  
        let inputVal = document.getElementById("temp_input").value;
        if (inputVal === "" || inputVal.slice(0, 8) === "(録音開始ボタン") return event;
        let str = document.getElementById("temp_input").value;
        str = str.slice(0, -1);
        event.record.newField.value = str;
        return event;
    });

    // 一覧画面では<button>と<textarea>のスペースを非表示にする
    kintone.events.on("app.record.detail.show", function () {
        document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[0].style.display = "none";
        document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[1].style.display = "none";
        document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[2].style.display = "none";
    });
})();

Web Speech API(speech recognition)の感想

良い点:無料で利用可能で認識自体が高性能。学習コストも低い。
悪い点:単語登録が出来ないのと、ブラウザのサポート範囲がまだ狭い。
結論 :マルチな機能にはなれないが、単発で使うのには十分な性能。

前回時点での課題点

  1. 各イベント処理(kintone.events.on)を追加して機能させる
  2. 音声入力アウトプット用フィールドを一行から複数行にする
  3. 録音前と録音開始時にメッセージを表示する
  4. 音声入力をしないで保存した場合に空白("")で上書きされるのを止める
  5. レコード詳細表示画面で表示される余計なスペースフィールドを非表示にする

改良後イメージ

【Before】
before1.png
【After】
after1.png

1.各イベント処理(kintone.events.on)を追加して機能させる

こちらは単純。レコード編集と、追加イベントで処理したいので前回に加えて
"app.record.create.show"を配列で追加。

kintone.events.on(["app.record.edit.show", "app.record.create.show"], function (event) {

2.音声入力アウトプット用フィールドを一行から複数行にする

前回は音声入力のアウトプットに<input>要素を使っていたが、<textarea>に変更。
innerText で録音開始前のメッセージも追加。

// 文字起こし用<textarea>作成
const tempForm = document.createElement("textarea");
tempForm.id = "temp_input";
tempForm.cols = "150";
tempForm.rows = "10";
tempForm.innerText = "(録音開始ボタンをクリックで音声受付を開始します)";
//tempForm.style.resize = "none";
kintone.app.record.getSpaceElement('form_space').appendChild(tempForm);

3.録音前と録音開始時にメッセージを表示する

(録音前のメッセージは 2.で実装済。)
録音開始時なので、Button.onclick()時にデフォルト指定。

// 録音ボタンの処理、ボタンの設置
startButton.onclick = () => {
    // 録音開始時のみメッセージを表示
    tempForm.innerText = "(音声を受け付けています)";
    recognition.start();
};

4.音声入力をしないで保存した場合に空白("")で上書きされるのを止める

こちら分かりづらいですが、<textarea>の値が "" 又はデフォルトの"(録音開始ボタン..."だった場合に returnすることで不要な上書きを防ぐことが可能です。

// 保存実行前に一時保存から取得してフィールドに移す。
kintone.events.on("app.record.edit.submit", function (event) {
    // 音声入力しなければreturn  
    let inputVal = document.getElementById("temp_input").value;
    if (inputVal === "" || inputVal.slice(0, 8) === "(録音開始ボタン") return event;
    let str = document.getElementById("temp_input").value;
    str = str.slice(0, -1);
    event.record.newField.value = str;
    return event;
});

5.レコード詳細表示画面で表示される余計なスペースフィールドを非表示にする

kintone JavaScript APIの標準機能ではスペースは非表示にすることが出来ません。しかし気持ちが悪いのでDOM操作で実現してしまいました。(実装は自己責任で)
【Before】
before2.png
【After】
after2.png

// 一覧画面では<button>と<textarea>のスペースを非表示にする
kintone.events.on("app.record.detail.show", function () {
    document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[0].style.display = "none"; // 録音ボタン設置用スペースフィールド
    document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[1].style.display = "none"; // <textarea>設置用スペースフィールド
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで文字列を逆順にする方法

コード

script.js
const text = "hello";

const reverseText = text.split("").reverse().join("");

console.log(reverseText);

解説

split(), reverse(), join() を組み合わせることで文字列を逆順にすることができます。

split()

split("")とすることで、文字列を文字ごとに配列にします。

reverse()

配列を逆順にします。

join()

join("")とすることで、配列内の文字列を連結します。

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

[JS] Class

classの初期化

constructor関数

constructor( )はclassが初期化されるタイミングで必ず呼ばれる関数です

new演算子

初期化を行います
初期化する作業を「インスタンス化」と言います


このコードだと

class TextAnimation {
    constructor(el) {
        alert(el);
    }
}

new TextAnimation('hello world');

Image from Gyazo

初期化されたタイミングでhello worldをalertします


thisを使ってnew演算子を使って初期化を行なった変数に格納することができます
この場合のthisはtaを表します

class TextAnimation {
    constructor(el) {
        this.el = el;
    }
}

const ta = new TextAnimation('hello world');
alert(ta.el);

Image from Gyazo

thisというプロパティに値を格納することによって
初期化した後の変数に値を格納したり、メソッドを格納したりすることができます


値を格納して(this.el = el;)、その値をメソッド内で使用することができる(console.log(this.el);)のがclassという演算子の利点です

class TextAnimation {
    constructor(el) {
        this.el = el;
    }
    log() {
        console.log(this.el);
    }
}

const ta = new TextAnimation('hello world');
ta.log();

Image from Gyazo


classを使ってリファクタリング

document.addEventListener('DOMContentLoaded', function () {
    const el = document.querySelector('.animate-title');
    const el2 = document.querySelector('.animate-title-2');
    const str = el.innerHTML.trim().split("");
    const str2 = el2.innerHTML.trim().split("");

    let concatStr = '';

    for(let c of str) {
        c = c.replace(/\s+/, '&nbsp;');
        concatStr += `<span class="char">${c}</span>`;
    }

    el.innerHTML = str.reduce((acc, curr) => {
        curr = curr.replace(/\s+/, '&nbsp;');
        return `${acc}<span class="char">${curr}</span>`;
    }, "");
    el2.innerHTML = str2.reduce((acc, curr) => {
        curr = curr.replace(/\s+/, '&nbsp;');
        return `${acc}<span class="char">${curr}</span>`;
    }, "");
});

Image from Gyazo

これを

document.addEventListener('DOMContentLoaded', function () {
    const ta = new TextAnimation('.animate-title');
    const ta2 = new TextAnimation('.animate-title-2');
});

class TextAnimation {
    constructor(el) {
        this.el = document.querySelector(el);
        this.chars = this.el.innerHTML.trim().split("");
        this.el.innerHTML = this._splitText();
    }
    _splitText() {
        return this.chars.reduce((acc, curr) => {
            curr = curr.replace(/\s+/, '&nbsp;');
            return `${acc}<span class="char">${curr}</span>`;
        }, "");
    }
}

こう書ける

_splitTextのように、先頭にアンダーバーがついているメソッドをPrivate Methodと呼び、
constructorのように何もついていないメソッドをPublic Methodと呼ぶことがある

_splitTextは、classの中以外で使わないでくださいという意味になります
実際はclass外でも使えますが、開発者同士のコミュニケーションの一環です

classとオブジェクト

classとobjectの記述

const obj = {
  first_name: 'Shun',
  last_name: 'Sato',
  printFullName: function() {
    console.log('hello');
  }
}

class MyObj {
  constructor() {
    this.first_name = 'Shun';
    this.last_name = 'Sato';
  }

  printFullName() {
    console.log('hello');
  }
}

const obj2 = new MyObj();

obj.printFullName();
obj2.printFullName();

Image from Gyazo


obj2にはprintFullNameメソッドが確認されないが

__proto__ というプロパティに格納されています
__proto__はブラウザが独自に設定しているプロパティです

Image from Gyazo

obj.printFullName();
obj2.__proto__.printFullName();

丁寧にこう書いても結果は同じになりますが、省略できる設定にデフォルトでなっているので書かなくてもOKです。

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

【Vue.js + TypeScript】autonakaでフリガナを自動的に別フィールドに入力させる

はじめに

  • Vue.js + TypeScriptを使い、ユーザー名入力フォームなどで日本語入力をした際に、自動で別フィールドにふりがなorフリガナを入力できるようにしました。

実装コード

<template lang="pug">
label 氏名
input#last_name(name='last_name' type="text" placeholder="山田" @input="handleLastNameInput")
input#first_name(name='first_name' type="text" placeholder="一郎" @input="handleFirstNameInput")

label フリガナ
input#last_name_furigana(name='last_name_furigana' type="text" placeholder="ヤマダ")
input#first_name_furigana(name='first_name_furigana' type="text" placeholder="イチロウ")
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import * as AutoKana from 'vanilla-autokana';

let autokanaLastName: any;
let autokanaFirstName: any;

@Component
export default class Hoge extends Vue {
  @Prop() userInfo!: {
    first_name: string,
    last_name: string,
    first_name_furigana: string,
    last_name_furigana: string
  };

  mounted() {
    autokanaLastName = AutoKana.bind('#last_name', '#last_name_furigana', { katakana: true });
    autokanaFirstName = AutoKana.bind('#first_name', '#first_name_furigana', { katakana: true });
  }

  handleFirstNameInput() {
    this.userInfo.first_name_furigana = autokanaFirstName.getFurigana();
  };
  handleLastNameInput() {
    this.userInfo.last_name_furigana = autokanaLastName.getFurigana();
  };
}
</script>

<style lang="scss">
</style>

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

スマホでも100vhを安定させる方法

はじめに

コンテンツの高さを100vhで指定した時に、pcだと問題なくても、スマホだとレイアウトが崩れることがある。
原因は、アドレスバーやキーボードなどがviewportの高さを圧迫してしまうから。
この記事では、スマホでも100vhを維持してレイアウトを崩さない方法をjsを使ってご紹介したいと思います。

ウィンドウの高さを取得して、min-heightで指定する

height:100vh;では、高さが可変してしまいますが、min-heightで指定してあげれば、それ以下のサイズになることはありません。

index.js
let sp_height = window.innerHeight;
document.documentElement.style.setProperty("--sp_height", `${sp_height}px`);

まず、1行目の記述でスマホの画面高をwindow.innerHeightで取得して、変数sp_heightに代入します。取得した高さsp_heightは、2行目の記述で、scssの変数--sp_heightに代入します。

index.scss
.sp_height{
  height: 100vh;
  min-height: var(--sp_height);
}

scssで、var(--sp_height);とプロパティの値に記述することで、jsとリンクさせることができます。
無事、min-heightが設定できました。

まとめ

min-heightを指定することで、アドレスバーやキーボードによってviewportが圧迫されたとしても、ウィンドウの高さ分はコンテンツの最低高として確保されるのでレイアウトが崩れる心配がありません。100vh指定は、スマホだと表示崩れなどが懸念されますが、回避策はいくつかあると思いますので今回記事で紹介した内容も一つの対策として、皆様の参考になれば幸いです。

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

機能検出、機能推論、UA文字列の利用の違いは何ですか?

Front-end-Developer-Interview-Questionsの内容についてひとつひとつ考えていく。
今回はJS Questionsから、「What's the difference between feature detection, feature inference, and using the UA string?」について。


Feature Detection

機能検出のはなし?(MDNの和訳がそうなっている)
下記MDNより。

あるブラウザーがあるコードのブロックに対応しているかどうかを調べ、対応しているか (またはしていないか) に応じて異なるコードを実行することで、ブラウザーが常に動作し、ブラウザーによってクラッシュやエラーが発生しないようにします。

Geolocation API (ブラウザーを実行している端末の位置情報を返します)は、 Navigator オブジェクトに含まれる geolocation プロパティを主なエントリーポイントとして持っています。
そこで、以下のようにしてブラウザーが位置情報機能に対応しているかどうかを検出できます。

if ("geolocation" in navigator) {
  navigator.geolocation.getCurrentPosition(function(position) {
    // Google Maps API を用いて現在位置をマップ上に表示します
  });
} else {
  // 位置情報がなくてもマップを表示できるようにします
}

ただし、機能検出のためのコードを毎回自分で書くよりも、確立された既存の機能検出ライブラリを使うほうが良いとのこと。

この用途では Modernizr が一般に利用されています。

Feature Inference

機能の推論。
xが存在する場合、yが存在すると見なす

機能の推論では、1つの機能を検出したため、他の機能を使用できると想定しています。たとえば、geolocation APIを検出した場合、ユーザーが最新のブラウザーを使用していると想定しているため、LocalStorageが利用可能になります。

if (navigator.geolocation) { 
    // geolocation possible.. do some stuff
}

if('localStorage' in window){
   window.sessionStorage.setItem("this-should-exist-too", 1);
}

推論はあんまりよくない。
利用したい機能ごとに機能検出を使用し、機能が利用できない場合に備えてフォールバック戦略を立てる方がよい。

UA String

MDN曰く

ユーザーエージェントを調べるのが良いことはめったにありません。問題を解決するには、もっと良い、もっと広く互換性のある方法が見つかるはずです。

あまりやらない方がよさそうですね。

var userAgent = window.navigator.userAgent;

// 例えばChromeを使ってると
// userAgent: Mozilla/5.0 (windows nt 6.3; wow64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36

// 調べ方
var userAgent = window.navigator.userAgent.toLowerCase();

// 基本的にはこれでOKらしい
if(userAgent.indexOf('msie') != -1) {
  console.log('お使いのブラウザはInternet Explorerですね!');
}

ただしChromeのユーザエージェント文字列に「safari」が入っていたり、
Edgeのユーザエージェント文字列に「Chrome」も「safari」も入っていたりするらしいので、
Edge→Chrome→safariの順で一致をみないといけなさそう。


参考URL

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

[JavaScript] いつものタグ入力UIを、少し簡単にするTagify

Tagify

2021年のトレンドになっているらしいのでやってみた。

ドキュメント

公式

GitHub

サンプル

See the Pen Tagify by ハタユウジ@コーポレートエンジニア (@shikumiya_hata) on CodePen.

html
<p>
  <label for='tags'>1. テキストボックス内にタグ</label>
  <input id="tags" name='tags' value='javascript, ライブラリ' autofocus>
</p>
<p>
  <label for='tagsOutside'>2. テキストボックスの外にタグ</label>
  <input id='tagsOutside' name='tags-outside' class='tagify--outside' value='javascript, ライブラリ' placeholder='タグを書くのもwhitelistから選択もできる'>
</p>
<p>
  <label for='tagCustom'>3. 見た目カスタマイズ</label>
  <input id="tagCustom" class='customLook' value='javascript, ライブラリ'><button type="button">+</button>
</p>

※CSSは長いので省略

js
/* 1. テキストボックス内にタグ */
var inputInside = document.querySelector('input[name=tags]');
var tagifyInside = new Tagify(inputInside);

/* 2. テキストボックスの外にタグ */
var inputOutside = document.querySelector('input[name=tags-outside]');
var tagifyOutside = new Tagify(inputOutside, {
  whitelist: ['javascript', 'js', 'ライブラリ', 'library'], //デフォルトで選択可能なタグ候補
  dropdown: {
    position: "input",
    enabled : 0 
  }
});

/* 3. 見た目カスタマイズ */
var inputCustomLook = document.querySelector('.customLook'),
    tagify = new Tagify(inputCustomLook, {
      dropdown : {
        position: 'text',
        enabled: 1
      }
    }),
    button = inputCustomLook.nextElementSibling;

button.addEventListener("click", onAddButtonClick)

// +ボタン押下時
function onAddButtonClick(){
    tagify.addEmptyTag() // 新しいタグを追加
}

ちなみに、valueを取り出すとこんな風になってます。

value
[
  {"value":"javascript"},
  {"value":"ライブラリ"}
]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vuexのgettersでstateをフィルタリングする方法

stateにあるJSONデータ、もしくはactionsで持ってきたデータをフィルタリングする方法。
今回はそもそもバックエンド側でフィルタリングしようねという話になったので、自分の環境では使用しなかった。
でもせっかく組んだし、もったいないので共有します(あんまり使い道は無いだろうけど・・・)

export const getters = {
    getFilteringList: (state) => (query) => {
      // queryの形式を[{key:'',value:''}...]に変換する
      let conversion = Object.entries(query).map(([key, value]) => ({'key': key, 'value': value}))
      // 条件に合致したデータを返す関数を定義(every関数なので、全てに合致する必要がある)
      function isMatchToAllConditions(data, conditions){
        return conditions.every(c => data[c.key] == c.value)
      }
      // null以外の要素を取り出す
      const conditions = conversion.filter(q => q.value && q.key)
      // フィルターをかける
      const searched = state.data.filter(list => {
        return isMatchToAllConditions(list, conditions)
      })
      // 出力
      return searched
    }
  }

getFilteringListにはコンポーネント側でqueryを投げてあげる必要がある。

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

firebase-admin を用いて最も単純な Firebase Cloud Firestore のデータを取得するコードサンプル

動機

Firestore のデータを取得する単純な事例があまり見当たらず、本家の資料を見てもいまいちピンとこなかったので、firebase-admin を使った最も単純なコードを書きたくなったため。

実行環境と利用ツール

Cloud Firestore 設定

まずはじめに Firebase コンソール(Web)から books コレクションと自動 ID を割り振ったドキュメントを作成します。

内容は極めて単純にしたかったので string 型の title というフィールドのみ追加します。

最後、値は javascript としています。

完成形の画面のスナップショットを貼っておきます。

Firestore data m.png

コレクション内のドキュメントを取得する最も単純なコード

// Ref : https://www.freecodecamp.org/news/the-firestore-tutorial-for-2020-learn-by-example/

const admin = require('firebase-admin');

const serviceAccount = require('./firestore-data-modeling-96db0-firebase-adminsdk-n3i63-0670471cc1.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

const db = admin.firestore();

const booksRef = db.collection('books');

// console.log(booksRef.get());

booksRef
  .get()
  .then((snapshot) => {
    const data = snapshot.docs.map((doc) => ({
      id: doc.id,
      ...doc.data(),
    }));
    console.log(data); 
    // [ { id: 'FY7PJJTw7EsS8x2hYphv', title: 'javascript' } ]
  });

実行結果

コード中にも記載がありますが JSON オブジェクトの配列を返します。

$ node books.js
[
  { id: 'FY7PJJTw7EsS8x2hYphv', title: 'javascript' }
]

参考資料

The JavaScript + Firestore Tutorial for 2020: Learn by Example

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

「;」や「\n」のあるなしで処理が変わるんだと実感した話

この記事で解決できるかもしれないエラー

SyntaxError: Unexpected identifier
JavaScriptだと珍しくないエラーのようです。Blocklyを使っている時にこのエラーが出た際はこの記事が参考になるかもしれません。

プログラムの流れ

ブロックを作成するファイル

Blocklyを使ってLOGOのようなビジュアル言語を作成しています。
スクリーンショット 2021-03-29 10.17.54.jpg

ブロックの定義はこんな感じ

turtleBlock.js
Blockly.Blocks['turtleforward'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("前に")
        .appendField(new Blockly.FieldNumber(0, -10, 10), "step")
        .appendField("進む");
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(60);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

実行されたの動きはこんな感じ

turtleBlock.js
Blockly.JavaScript['turtleforward'] = function(block) {
    var number_step = block.getFieldValue('step');
    var fowardCode = `TurtleForward(${number_step})`; 
    return fowardCode;
};
  • 引数の数字を読み取る
  • 別の場所で定義されているTurtleForward()を実行するためにforwardCodeを定義
  • returnで渡す

実行するファイル

turtleBlock.jsから値を受け取り、実行します。

turtleCommand.js
TurtleForward = (value) => {
    console.log('実行された'+value);
    var code = 'TurtleForward(turtle,'+value+')';
    ggbApplet.evalCommand(code);
}

実際の挙動

ブロックを並べて実行すると

スクリーンショット 2021-03-29 10.55.20.jpg

画像のような挙動が確認されます。
しかし、ブロックを繋げて実行すると
スクリーンショット 2021-03-29 10.54.32.jpg

上述したエラーが出てしまいます。

解決方法

タイトルにもありますが、ブロックの定義の際に;\nをつけていないことが原因でした。試しにブロックによって作成されるコードを表示させてみると

TurtleForward(1)TurtleForward(1)

定義されているTurtleForward()を認識することができず、エラーを起こしてしまうようです。

turtleBlock.js
Blockly.JavaScript['turtleforward'] = function(block) {
    var number_step = block.getFieldValue('step');
    console.log(number_step+'進む');
    var fowardCode = `TurtleForward(${number_step})\n`; //変更点
    return fowardCode;
  };

あるいは

turtleBlock.js
Blockly.JavaScript['turtleforward'] = function(block) {
    var number_step = block.getFieldValue('step');
    console.log(number_step+'進む');
    var fowardCode = `TurtleForward(${number_step});`; //変更点
    return fowardCode;
  };

のように\n;を定義された関数を文字列として扱ったものを最後に入れることでエラーを解決することができました。

;

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

JavaScriptでDropbox APIから画像を読み込む

とりあえず動いた喜びと感動とメモ帳書きとして

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Dropbox Test</title>
    <script src="https://unpkg.com/dropbox/dist/Dropbox-sdk.min.js"></script>
</head>
<body>
<h1>Dropbox Test</h1>
<script>

    let accessToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    let dbx = new Dropbox.Dropbox({accessToken: accessToken});
    let dir_path = "/Photos"
    dbx.filesListFolder({path: dir_path})
        .then(function (response) {
            imageLoad(response);
        })
        .catch(function (error) {
            console.error("error " + error);
        });
    function imageLoad(json)
    {
        let n = json.result.entries.length;
        for(let i = 0;i<n;i++)
        {
            let fileData = json.result.entries[i];

            dbx.filesGetTemporaryLink({"path": fileData.path_lower})
                .then(function(response) {
                    let img = document.createElement('img');
                    img.src = response.result.link;
                    document.body.appendChild(img);
                })
                .catch(function(error) {
                    console.log("got error:");
                    console.log(error);
                });
        }
    }
</script>
</body>
</html>

サムネールは画像をBlobで受けとるapiがありますが、
直接画像を取るには、json内のリンクを取るしかないっぽい。

参考

知識ゼロだけど、Dropbox APIを使用したい
https://qiita.com/Ella_Engelhardt/items/c33f08b6b427eab8b310

dropbox-sdk-jsを使ってフォルダ内アイテムの共有リンクを取得
https://kittagon.hateblo.jp/entry/2018/08/13/000916

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

Microsoft Graph APIのリフレッシュトークン取得

リフレッシュトークンとは? Microsoft Graph APIを使用するには、アクセストークンを取得する必要があります。ただ1度アクセストークンを取得したとしても有効期限は1時間のため、すぐに使えなくなってしまいます。ここで必要になるのがリフレッシュトークンです。リフレッシュトークンでアクセストークンを更新した際に新しいリフレッシュトークンも発行されます。その度に新しいリフレッシュトークンに乗り換えていくことによってアクセストークンを継続して取得できるようになり、APIを使い続けることが可能になります。 この記事ではリフレッシュトークンを取得する方法をできる限り簡潔に紹介します。 前提条件 まず大前提として、AzureポータルのAzure Active DirectoryからMicrosoft Graphのアプリ登録をする必要があります。登録を終えた後、下記4点が必要になりますのでどこかに記録しておきます。 ・クライアントID ・シークレットID ・リダイレクトURL ・テナントID codeを取得する リフレッシュトークンを取得するためにPOSTリクエストを送るのですが、その際に「code」というものが必要になります。codeを取得するには、以下のURLをブラウザに貼り付けます。 https://login.microsoftonline.com/{テナントID}/oauth2/v2.0/authorize?client_id={クライアントID}&response_type=code&redirect_uri={リダイレクトURL}&response_mode=query&scope=offline_access%20user.read%20mail.read%20calendars.readwrite&state=12345 そうすると、新しいページにリダイレクトされます。 その際に、新しくなったURLの「code=」の部分を確認し控えておきます。 リフレッシュトークンの取得方法 ここでは、node.jsでのコードになります。(axiosを使用しております) const redirect_uri = "リダイレクトURL" const client_id = "クライアントID" const Tenant = "テナントID" const client_secret = "シークレットID" const axios = require("axios"); const qs = require("querystring"); const token_endpoint = "https://login.microsoftonline.com/" + Tenant + "/oauth2/v2.0/token"; let code = "③にて取得したcode" async function main() { let data = { grant_type: "authorization_code", client_id: client_id, client_secret: client_secret, redirect_uri: redirect_uri, code: code, }; let opt = { method: "POST", data: qs.stringify(data), url: token_endpoint, }; let result = await axios(opt) console.log(result.data) } main() 上記のコードにて、リフレッシュトークンを取得することができます。 まとめ リフレッシュトークンを取得できれば、実際にAPIを用いて、カレンダーやメールなどさまざまな情報の取得や更新などができるようになります。 かなりざっくりとした説明になってしまいましたが、最後まで呼んでいただきありがとうございます! (続編に繋ぐ)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Microsoft Graph APIのリフレッシュトークン取得

リフレッシュトークンとは?

Microsoft Graph APIを使用するには、アクセストークンを取得する必要があります。ただ1度アクセストークンを取得したとしても有効期限は1時間のため、すぐに使えなくなってしまいます。ここで必要になるのがリフレッシュトークンです。リフレッシュトークンでアクセストークンを更新した際に新しいリフレッシュトークンも発行されます。その度に新しいリフレッシュトークンに乗り換えていくことによってアクセストークンを継続して取得できるようになり、APIを使い続けることが可能になります。
この記事ではリフレッシュトークンを取得する方法をできる限り簡潔に紹介します。

前提条件

まず大前提として、AzureポータルのAzure Active DirectoryからMicrosoft Graphのアプリ登録をする必要があります。登録を終えた後、下記4点が必要になりますのでどこかに記録しておきます。
・クライアントID
・シークレットID
・リダイレクトURL
・テナントID

codeを取得する

リフレッシュトークンを取得するためにPOSTリクエストを送るのですが、その際に「code」というものが必要になります。codeを取得するには、以下のURLをブラウザに貼り付けます。

https://login.microsoftonline.com/{テナントID}/oauth2/v2.0/authorize?client_id={クライアントID}&response_type=code&redirect_uri={リダイレクトURL}&response_mode=query&scope=offline_access%20user.read%20mail.read%20calendars.readwrite&state=12345

そうすると、新しいページにリダイレクトされます。
その際に、新しくなったURLの「code=」の部分を確認し控えておきます。

リフレッシュトークンの取得方法

ここでは、node.jsでのコードになります。(axiosを使用しております)

const redirect_uri = "リダイレクトURL"
const client_id = "クライアントID"
const Tenant = "テナントID"
const client_secret = "シークレットID"

const axios = require("axios");
const qs = require("querystring");

const token_endpoint = "https://login.microsoftonline.com/" + Tenant + "/oauth2/v2.0/token";

let code = "③にて取得したcode"

async function main() {
  let data = {
    grant_type: "authorization_code",
    client_id: client_id,
    client_secret: client_secret,
    redirect_uri: redirect_uri,
    code: code,
  };

  let opt = {
    method: "POST",
    data: qs.stringify(data),
    url: token_endpoint,
  };

  let result = await axios(opt)
  console.log(result.data)
}
main()

上記のコードにて、リフレッシュトークンを取得することができます。

かなりざっくりとした説明になってしまいましたが、最後まで呼んでいただきありがとうございます!

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

すごいReactパッケージ5選

本記事は、Varun Chilukuri氏による「Five awesome React packages to check out」(2020年9月8日公開)の和訳を、著者の許可を得て掲載しているものです。

すごいReactパッケージ5選

小さくてもインパクトのある変更で、あなたのアプリを競合他社から引き離します。

Image for post

1. React Loader Spinner

待つのが好きな人はいません。今やユーザーは最低限の条件として、ウェブサイトが高速であることを求めています。アプリが重いか遅い場合は、コンテンツの読み込み中、この最新のローディングアニメーションを表示しましょう。アプリの美観を向上させるだけでなく、ユーザーを維持するのにも役立ちます。

React Loader Spinner

このパッケージで提供する全ローディングアニメーション

npmまたはyarnから直接インストールします。

npm i react-loader-spinner
//or
yarn add react-loader-spinner

loader.js

次に、必要なimport文を追加します。

import Loader from 'react-loader-spinner'

loader.js

そして、以下をプロジェクトに追加します。

<Loader
   type="Puff"
   color="#00BFFF"
   height={100}
   width={100}
   timeout={3000} //3 secs
/>

loader.js

詳細については、ドキュメントを参照してください。

react-loader-spinner
react-spinner-loaderは、非同期の待機中の動作を実装できる、単純なReact SVGスピナーコンポーネントです。
www.npmjs.com

2. React Animated Burgers

このパッケージは、ナビゲーションバーにアニメーション付きメニューアイコンを追加します。無数のアイコンとアニメーションが用意されています。とても簡単に、カスタマイズとプロジェクトへの追加ができます。私は喜んで何度も使っています。

React Animated Burgers

このパッケージで提供するさまざまなアニメーションの例

他のnpm/yarnパッケージと同様に、1行で簡単にインストールできます。

npm i react-animated-burgers
//or
yarn add react-animated-burgers styled-components

hamburger-menu.js

アニメーションアイコンを1つ選択してimportするだけで、プロジェクトに追加できます。

import { HamburgerSpin } from 'react-animated-burgers'

hamburger-menu.js

そうすると、ヘッダーやナビゲーションバーに簡単に追加できます。

<HamburgerSpin
   buttonColor="red" //optional
   barColor="#F5F5F5" //optional
   {...{ isActive, toggleButton }}
/>

hamburger-menu.js

最も正確で新しい情報については、ドキュメントを参照してください。

react-animated-burgers
パッケージのインストールは、npm i -S react-animated-burgers styled-components またはyarn add react-animated-burgers...
www.npmjs.com

3. React Responsive Carousel

多くのウェブサイトでは、カルーセルを設置して、商品、チームメンバー、会社に関する一般的な情報を表示しています。サイトにカルーセルを設置したいと思っているなら、おそらく多くの中途半端またはいまいちなパッケージを見たことがあるでしょう。これは、他のパッケージとは違い、インパクトがあり軽量、完全にカスタマイズ可能です。

React Responsive Carousel

カルーセルの動作デモ

パッケージをインストールします。

npm i react-responsive-carousel
//or
yarn add react-responsive-carousel

carousel.js

import文を追加して、プロジェクトに追加します。

import { Carousel } from 'react-responsive-carousel'
import "react-responsive-carousel/lib/styles/carousel.min.css";

carousel.js

以下をウェブサイトに簡単に追加できます。

<Carousel>
   <div>
      <img src="assets/1.jpeg" />
      <p className="legend">Legend 1</p>
   </div>
   <div>
      <img src="assets/2.jpeg" />
      <p className="legend">Legend 2</p>
   </div>
   <div>
      <img src="assets/3.jpeg" />
      <p className="legend">Legend 3</p>
   </div>
</Carousel>

carousel.js

このパッケージは、制御方法が多く自由度が高いです。このプロジェクトを十分に活用するには、GitHubリポジトリなどを見てください。

react-responsive-carousel
インパクトがあり軽量、完全にカスタマイズ可能なReactアプリ用カルーセルコンポーネントです。レスポンシブモバイルフレンドリーです...
www.npmjs.com

4. React CountUp

企業の統計情報をウェブサイトに表示することは、かつてない程に簡単になりました。このパッケージでは、動的カウンターを使って、印象的な数字を目立たせて強調表示できます(溶け込んでしまう静的テキストとは違います)。

React CountUp

パッケージをインストールします。

npm i react-countup
//or
yarn add react-countup

countup.js

以下をプロジェクトファイルの先頭に追加して、プロジェクトに追加します。

import CountUp from 'react-countup';

countup.js

以下は、3つの簡単な使用例です。

<CountUp end={100} />
<CountUp delay={2} end={100} />
<CountUp duration={5} end={100} />

countup.js

より高度な機能と自由度については、パッケージのページを参照してください。

react-countup
CountUp.jsのReactコンポーネントラッパーです。
www.npmjs.com

5. React Markdown

Markdown言語が提供する効率性とシンプルさが好きな人にとっては朗報です。ReactコードでMarkdownを使う簡単な方法があります。このパッケージを使うだけです!

React Markdown

npmでインストールします。

npm i react-markdown

markdown.js

注:残念ながらこのパッケージは、yarnによるインストールをサポートしていません。

必要な文をコードに追加します。

const ReactMarkdown = require('react-markdown')

markdown.js

使い始めましょう!

const React = require('react')
const ReactDOM = require('react-dom')
const ReactMarkdown = require('react-markdown')

const input = '# This is a header\n\nAnd this is a paragraph'

ReactDOM.render(<ReactMarkdown source={input} />, document.getElementById('container'))

markdown.js

最も正確で新しい情報については、公式ページを参照してください。

react-markdown
Markdownを純粋なReactコンポーネントとしてレンダリングします。デモはこちらで見られます。https://rexxars.github.io/react-markdown/ react-markdown...
www.npmjs.com

おわりに

この記事が参考になり、アプリに追加したいと思うパッケージが1つでもあったなら幸いです。

Image for post

この記事などで使われているコードはすべて、私のGitHubレポジトリにあります。

この記事が役に立った場合は、フォローをお願いします!React.jsに関する記事がもっとあります。フィードバックやコメントもお待ちしています。

JavaScriptを分かりやすく解説

私たちが3つのパブリケーションとYouTubeチャンネルを持っていることを知っていますか?すべてのリンクはこちらplainenglish.io

翻訳協力

この記事は以下の方々のご協力により公開する事ができました。改めて感謝致します。

Original Author: Varun Chilukuri
Original Article: Five awesome React packages to check out
Thank you for letting us share your knowledge!

選定担当: @gracen
翻訳担当: @gracen
監査担当: -
公開担当: @gracen

ご意見・ご感想をお待ちしております

今回の記事はいかがでしたか?
・こういう記事が読みたい
・こういうところが良かった
・こうした方が良いのではないか
などなど、率直なご意見を募集しております。
頂いたお声は、今後の記事の質向上に役立たせて頂きますので、お気軽に
コメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
皆様のメッセージをお待ちしております。

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

税込から税抜を求めるには切り上げ?切り下げ?→ケースバイケースで要注意な件

4月から税込み表示が義務化され、また世のエンジニアが苦労する中、逆に税込から税抜を求めるの思った以上に苦労した話です。
一般的な小売では税込から税抜を求める状況はないと思いますが、相対取引で定価が無い場合は税込価格から決まることはザラです。

具体例として10096円〜10099円の間の価格でお話ししますと
10097円は÷1.1して切り捨てた9179円が正解です。
10099円は÷1.1して切り上げた9181円が正解です。

なぜなら双方とも誤って切り上げたり、切り下げると共に9180円になり、そこから税込価格を逆算すると10098円一択となってしまい元に値と矛盾するからです。ちなみに

10098円は割り切れる整数なので、切り捨て切り上げ関係なく、9180円の一択になります。
10096円は切り捨てて9178円、切り上げて9179円としてどちらでも良いのですが、それらからの税込み価格の計算は、端数の取扱次第で10095〜10097円まで幅広く解釈することができます。

これらを踏まえ実装としては、税抜価格netが最大化する按分になるceilでまず計算してみて、そこから税込の逆算が矛盾を起こす場合のみ、floorを採用する条件分岐が必要になります。
MySQLなら以下の通りです。

mysql> SELECT
    -> taxed,
    -> case when floor(ceil(taxed/1.1)*1.1)=taxed or ceil(ceil(taxed/1.1)*1.1)=taxed then ceil(taxed/1.1) else floor(taxed/1.1) end as net
    -> FROM (
    -> SELECT 10096 AS taxed UNION
    -> SELECT 10097 AS taxed UNION
    -> SELECT 10098 AS taxed UNION
    -> SELECT 10099 AS taxed
    -> ) prices;
+-------+------+
| taxed | net  |
+-------+------+
| 10096 | 9179 |
| 10097 | 9179 |
| 10098 | 9180 |
| 10099 | 9181 |
+-------+------+
4 rows in set (0.01 sec)

MySQLは問題ありませんでしたが、実装には浮動小数点の問題があり要注意です。
端的に以下に例示すると100円の税込みは110円のはずですが、どちらも111円になります。体感的にはもはやバグです。

JavaScriptによる例
Math.ceil(100*1.1) // 111
pythonによる例
import math
math.ceil(100*1.1) # 111

このリスクに対応するため8桁以降を丸めた処理を挟んで以下のようになりました。

pythonによる例
from math import floor, ceil
{taxed: ceil(round(taxed / 1.1, 8)) if floor(round(ceil(round(taxed / 1.1, 8)) * 1.1, 8)) == taxed or ceil(round(ceil(round(taxed / 1.1, 8)) * 1.1, 8)) == taxed else floor(round(taxed / 1.1, 8)) for taxed in [10096, 10097, 10098, 10099]}
# {10096: 9179, 10097: 9179, 10098: 9180, 10099: 9181}

1万円までの全価格を走査して統計とったスクリプトを書いたので、貼っておきます。
価格(整数)の集合全体の8割は割った後に切り上げ切り下げどちらでも良いのですが、残り1割ずつは切り上げ限定か切り下げ限定の困ったちゃん価格なのが分かります。

from math import floor, ceil
import pprint
result = {}

for taxed in range(100, 10100):
    recalc = {
        "FF": floor(round(floor(round(taxed / 1.1, 8)) * 1.1, 8)),
        "CF": floor(round(ceil(round(taxed / 1.1, 8)) * 1.1, 8)),
        "FC": ceil(round(floor(round(taxed / 1.1, 8)) * 1.1, 8)),
        "CC": ceil(round(ceil(round(taxed / 1.1, 8)) * 1.1, 8)),
    }
    key = tuple(k for k, v in recalc.items() if v == taxed)
    if len(key) == 0:
        print(taxed)
        raise
    result[key] = [*result.get(key, []), taxed]
    print(taxed, key, recalc)

pprint.pprint({k: {
    "min": min(v),
    "max": max(v),
    "len": len(v), } for k, v in result.items()})

税抜き価格の計算は税率で割って丸めるだけ、そんなふうに考えていた時期が俺にもありました。。。

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

JavaScript 文字列を配列に変換する

配列の要素がダブルクォーテーションで囲まれている場合

let arrayStr = '["A", "B", "C"]';
let array = JSON.parse(arrayStr);

配列の要素がシングルクォーテーションで囲まている場合

let arrayStr = "['A', 'B', 'C']";
let array = JSON.parse(arrayStr.replace(/'/g, '"'));

両方対応

let array = arrayStr.replace(/[\[\]"' ]/g, '').split(',');

参考記事

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

thisなきJS エラーあり

はじめに

 JSでTODOアプリをつくり、そのやり方を教材として書き起こしていた。教材の仕様に沿ってアロー関数をつかったらうまく動作しなくなった。なんだこれは。少しだけ考えた。

問題

 問題が発生したのはTODOの内容をクリックして、既存のタスクを編集する箇所。今までのfunctionではOKだったが、アロー関数にするとなんだかおかしい。

コード

 問題点を明らかにするためにその箇所だけを抜き出して別にコードを書いた。「テスト」をクリックするとアロー関数のやつが実行されて、「テスト2」だと普通の関数が実行される。
 テストをクリックして、入力フォームのカーソルを外すと

 「Uncaught TypeError: Cannot read property 'classList' of null」

 というエラーが出てくる。
 

test.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8"/>
    <link rel ="stylesheet" href="todo.css">
    <title>イベントハンドラテスト</title>
</head>
<body>
  <header>
    <h1>TODOテスト</h1>
  </header>
  <div class="container">
    <div class="todo-container">
      <ul class="todo-list">
        <li>
          <span class="todo-content1">テスト</span>
        </li>
        <li>
          <span class="todo-content2">テスト2</span>
        </li>
      </ul>
    </div>
  </div>
    <script src = "test.js"></script>
</body>
</html> 
const editTodo = (e) => {
  let itemToEdit = e.target;
  if(!itemToEdit.classList.contains('on')) {
    itemToEdit.classList.add('on');
    let contentBeforeEdit = itemToEdit.textContent;
    itemToEdit.innerHTML = '<input type="text" class="editbox1" value="'+contentBeforeEdit+'" />';
    const editContent1 = document.querySelector('.editbox1');

    const saveTodoContent  = (e) => {
        let itemToSave = e.target;
        itemToSave.parentNode.classList.remove('on');
        let txtvalue = itemToSave.value;
        if (txtvalue ==''){
         txtvalue = itemToSave.defaultValue;
        }
        itemToSave.parentNode.innerHTML = txtvalue;

    }
    editContent1.addEventListener('blur',saveTodoContent);
  }
}

function editTodo2(e) {
  if(!this.classList.contains("on")) {
    this.classList.add("on");
    let contentBeforeEdit = this.textContent
    this.innerHTML = '<input type="text" class="editbox2" value="'+contentBeforeEdit+'" />'
    const editContent2 = document.querySelector(".editbox2")

    let saveTodoContent = function(){
      this.parentNode.classList.remove('on')
      let txtvalue = this.value
      if (txtvalue ==""){
       txtvalue = this.defaultValue
      }
      this.parentNode.innerHTML = txtvalue

    }
    editContent2.addEventListener("blur",saveTodoContent)
  }
}

//Select DOM
const todoContent = document.querySelector('.todo-content1');
todoContent.addEventListener('click', editTodo);
const todoContent2 = document.querySelector('.todo-content2');
todoContent2.addEventListener('click', editTodo2);

問題を追う

 カーソルを外した時に「classListがnullですよ」というエラーが発生する。ちょうどこの行である。

 const saveTodoContent  = (e) => {
//
      itemToSave.parentNode.classList.remove('on');
//

 Chromeのデベロッパーモードで少しずつ検証していった。そうするとカーソルを外す時に実行されるイベントsaveTodoContentが2回実行されていることがわかった。そして2回目の実行時にエラーが発生していた。なぜだ。

 だいたいはthisが原因だった。

原因

 この機能では項目の状態を知る必要がある。つまり「タスクが現在入力モードなのか否か」ということを知りたいのだ。コード上ではclasslistにonというclassを付与したり外したりしてそれを操作している。
 今まではその検知にthisを用いていたが、今回アロー関数に書き換えたことによってthisが使えなくなり、別の記法で書き換えた。そこがよくなかったのだ。

 ここである。

変更前
function editTodo2(e) {
  if(!this.classList.contains("on")) {
// .. クリックときに編集モードでなければ以下を実行
変更後
const editTodo = (e) => {
  let itemToEdit = e.target;
  if(!itemToEdit.classList.contains('on')) {
// .. クリックときに編集モードでなければ以下を実行

this/e.target

thisとe.targetはなにが違うのか。thisは場面によってさまざまに容態を変化させるが、ここでは以下の記事を参考にするならば「関数を呼び出している元のオブジェクト」である。

 e.targetとはクリックしたときの物体である。テキスト文字であったり、入力フォームであったりする。

 thisの中身はタスクが編集モードか否かでも変わらない。しかしe.targetは中身が変わる。ためしにconsole.logで読んでみよう。

const editTodo = (e) => {
  let itemToEdit = e.target;
  console.log(e.target);
  console.log(this);

//


function editTodo2(e) {
  console.log(e.target);
  console.log(this);

 
スクリーンショット 2021-03-29 5.25.11.png

上4行はアロー関数のほう。下はいままでの関数だ。たしかになんか違っていた。

どうしたか

 アロー関数にはイベントが実行される条件を増やした。

const editTodo = (e) => {
  let itemToEdit = e.target;
  if(!itemToEdit.classList.contains('todo-content1')){
    return;
  }
  if(!itemToEdit.classList.contains('on')) {
//....

 これで問題は解決した。

おわりに

 文章がまとまらないままこれを書き出してしまった。それでもどこかに残しておかないと、後々自分が困ってしまう。

 もしかしたら追記するかもしれないししないかもしれない。

 

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

化物語の予告風デザインのホームページを作った

はじめに

タイトルの通りです。
なんとなく思いついたのでそれっぽいデザインで作ってみました。
モバイル用の実装はしていないのでPCのみです。
コードの書き方とかディレクトリ構成とか色々適当なので分かりにくかったらすみません。
それぞれのページにダミーデータみたいなのを入れてあります。

GitHubにありますので良かったら。
ソースコード

2021-03-29-050426_1366x768_scrot.png
こんな感じ

参考

今回作ったものの中身などは特に解説はしませんので、GitHubを見てください。
ですが、制作過程で参考にしたサイトなどを紹介します。

ブログページ関連

CSS

その他

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

Web API DOMとEvent Flowを理解する

はじめに

ふと、DOMとは何か理解したくなったので勉強してまとめたいと思います。
DOM全体だとかなり長くなってしまうので、今回はよくある下記のようなコードがどのような仕組みで動いているのか理解したいと思います。

let element = document.getElementById('el');
element.addEventListener('click', function() {})

DOM: Document Object Model

DOMは、HTMLやXMLを操作するためのインターフェイスです。
XMLに対してDOM操作って個人的には馴染みのないものですね。

Webページを表現するHTMLは、あくまで"文書"です。
DOMは、HTMLをオブジェクトとして操作・変更するためのインターフェイスであり、その操作を行う言語がJavaScriptです。
DOM操作を主に使用される言語がJavaScriptなだけであり、DOM操作は他の言語でも可能です。
例えばPHPには、DOM操作するためのクラスが用意されています。
https://www.php.net/manual/ja/book.dom.php

<?php
$doc = new DOMDocument();
$doc->loadHTML('<html><body id="test">Test<br></body></html>');
echo $doc->getElementById('test')->tagName; // body
?>

DOMインターフェイス

DOMで提供されるインターフェイスは様々なものがありますが、
いくつか取り上げてまとめていきたいと思います。

EventTarget

DOMを構成する基底のインターフェースです。
イベントを登録したり、DOM操作の対象になるオブジェクトです。
イベントを追加するaddEventListener()はEventTargetで実装されているメソッドです。

Node

DOMを構成するインターフェースの1つです。EventTargetのメソッドとプロパティを継承しています。
DOMのインターフェイスは、基本的にNodeを継承しています。

例えば以下のものはNodeに含まれます。

  • HTMLElement
  • テキスト
  • コメント
  • 属性

DOMは、HTML内のオブジェクトをNodeとして扱います。
以下のようなHTMLを分類してみるとこのような感じになると思います。

  • html -> element node
  • body -> element node
  • h1 -> element node
  • メイン -> テキスト node
  • <!-- メイン --> -> コメント node
<html>
  <body>
    <h1>タイトル</h1>
    <!-- メイン -->
  </body>
</html>

Document

DOM ツリーであるウェブページのコンテンツへのエントリーポイントとして働きます
https://developer.mozilla.org/ja/docs/Web/API/Document

<html>から始まるDOMツリーにアクセスするための、エントリポイントです。
Nodeを継承していて、ドキュメント全体の情報を検索したりすることができます。

<html>
  <body>
    <h1>タイトル</h1>
    <img src="hoge.jpg" alt="">
  </body>
</html>

<script>
let images = document.images; // HTMLCollection[img]
</script>

DocumentやWindowオブジェクトは、Nodeを継承していて、EventTargetで実装されているaddEventListenerといったメソッドにアクセスすることができます。

Event Flow

インターフェイスを追っていくことで、addEventListenerが実装されている基底のインターフェイスまで学ぶことができました。
次に、イベント処理がどのように行われるのかみてみたいと思います。

イベントとは

そもそもイベントとは何か、簡単にまとめたいと思います。
Webページで行われる状態の変化や操作全般を指しています。
マウスカーソルの移動やボタンのクリック、動画の再生もイベントととして扱うことができます。
どんなイベントがあるか、こちらからわかりやすく一覧でみることができます。量は大変多いです。
WebVRに関連したイベントもありますね?
https://developer.mozilla.org/ja/docs/Web/Events

イベントフロー

処理の流れを整理するためにイベントフローを理解するのが良さそうです。
Event Flow
画像は、本記事の参考文献でもあるW3Cのページからお借りしました。
https://www.w3.org/TR/DOM-Level-3-Events/

イベントが発生してからEventListenerの処理が行われるまで3つのフェーズがあります。

  • capture phase
    WindowオブジェクトからターゲットとなるElementをツリー構造を下っていくように捕捉します。

  • target phase (at-target phase)
    ターゲットを見つけたフェーズです。 イベントの種類をみてbubble phaseへ移すか判断します。

  • bubble phase
    capure phaseとは逆に、イベントが起こったElementから親Nodeを辿ってイベントを伝搬させます。
    イベントハンドラは基本的にバブリングフェーズに登録されるので、ユーザーが定義したハンドラの実行もこのフェーズで行われます。

伝播ということがどういうことかわかりづらかったので例に以下のようなコードを用意しました。
bodyh1には同じハンドラを登録しています。
h1をクリックした時に、consoleに2回以下のようなログが記録されるはずです。
[object HTMLHeadingElement] clicked
これはイベントのバブリングによってbodyに登録されたハンドラが実行されたことによります。
e.targetは、イベントが発生した要素を示すので、bodyがクリックされた訳ではなく、h1で発生したイベントが伝播したことがわかると思います。

<html>
  <body id="body">
    <h1 id="h1">タイトル</h1>
  </body>
</html>

<script>
  function handler(e) {
    console.log(`${e.target} clicked`);
  }
  document.getElementById('body').addEventListener('click', handler);
  document.getElementById('h1').addEventListener('click', handler);
</script>

なお、イベントの伝播をせずにターゲットのElementだけで処理をさせたいような場合には、e.stopPropagation()を書いて上げることで実現可能です。

function handler(e) {
  e.stopPropagation()
  console.log(`${e.target} clicked`);
}

まとめ

冒頭に紹介したコードを改めて読んでまとめとします。

let element = document.getElementById('el');
element.addEventListener('click', function() {})

elがclickされた時の流れ

  • クリックイベントが発生する
  • イベントは、Windowオブジェクトから子Nodeに伝播する
  • ターゲットとなるelを見つける
  • バブリングフェーズに登録された匿名関数を実行する
  • (親Nodeへイベントが伝搬する)

参考

DOMの紹介
https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model/Introduction

DOM EventTarget
https://developer.mozilla.org/ja/docs/Web/API/EventTarget

DOM Node
https://developer.mozilla.org/ja/docs/Web/API/Node

ブラウザの仕組み全般
https://www.html5rocks.com/ja/tutorials/internals/howbrowserswork/#The_rendering_engine

イベントフロー
https://www.w3.org/TR/DOM-Level-3-Events/

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

【PHP】GET・POST サーバー 作成

今回はphpのget・postサーバーの作成について書きます。
正直、今回の記事は自身のメモとしての意味合いが大きいです。
似たようなことが書かれた記事は多いですし、コードの内容も多くないため簡潔に書いていこうと思います。

実装環境

今回はそんなに手間をかけるつもりはないため、xamppを使用します。
VSCodeを使用してコードを書きましたが、コマンドでビルドが必要だとかいうこともないので、テキストエディターは何でも良いです。

GET・POST サーバー

index.php
<?php
if (isset($_POST['post_sample'])) {
    echo $_POST['post_sample'];
}

if (isset($_GET['get_sample'])) {
    echo $_GET['get_sample'];
}

PHP側のコードはこれだけです。
$_POST['~']でPOSTで送られてきた値を取得し、$_GET['~']でGETで送られてきたデータを取得します。

クライアント側(html)

動作確認のために次はクライアント側のhtmlのコードを作成します。

index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UFT-8">
    <title>get_post_sample</title>
</head>

<body>
    <form action="index.php" method="GET">
        <input type="text" name="get_sample" value="get_sample_text">
        <input type="submit" value="get">
    </form>
    <form action="index.php" method="POST">
        <input type="text" name="post_sample" value="post_sample_text">
        <input type="submit" value="post">
    </form>
</body>

</html>

細かい説明は省きますが、formのinputを使用することでGET・POSTのいずれも送信することができます。(見てわかるかもしれませんが、methodの部分でGETとPOSTを定義してます)

動作確認

xamppを起動してページを呼び出せば下記の様なページになります。
image.png
ボタンのgetを押せばGETで送信され、ボタンのpostを押せばPOSTで送信されます。

GET送信時

image.png
php側の処理はechoしているだけなので、画面は送信したget_sample_textだけが表示されます。URLを確認してもらえばわかるかともいますが、GETの挙動としてパラメータがURLに表示されています。

POST送信時

image.png
php側で設定してる内容はGETとほぼ同じなので、挙動に大きな差は見られません。
POST時の挙動として、GETと違いパラメータがURLには表示されません。

クライアント側(JavaScript)

今回は、JavaScriptでもクライアント側を作成してみます。

script.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UFT-8">
    <title>get_post_sample</title>
</head>

<body>
    <script>
        function do_post() {
            let xhr = new XMLHttpRequest();
            xhr.open('POST', 'index.php', true);
            xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
            let request = "post_sample=post_text_by_js";
            xhr.send(request);
            //post送信後のレスポンス
            xhr.onreadystatechange = function () {
                alert(xhr.responseText);
            }
        }
    </script>
    <input type="button" value="post" onclick="do_post()">
</body>

</html>

処理としてはXMLHttpRequestオブジェクトを生成してPOSTを送信(.send())します。
そのあと、onreadystatechangeに設定された内容の通りに、PHP側から送られてきたPOSTのレスポンスを処理します。

動作確認

image.png
作成したページを開き、postボタンを押下するとonreadystatechangeで処理を設定した通り、アラートが表示されます。
今回の場合は、アラートで表示される内容はphpのページで表示される内容と同じなので、postで送信した内容がそのまま表示されることになります。(php側のソースで、POSTで送られてきた内容をそのままechoしているだけになっているため)
htmlで作成した場合と違い、画面遷移することはありません。

終わりに

今回はGET・POSTのサーバー側と、クライアント側を二種類書きました。
簡潔に書くと言った割に長いような…
PHPはあまり触ってないので、個人的にはもう少し腕を磨きたいと思っています。
あと、GET・POSTというとJavaのサーブレットを仕事でやったことがありましたが、結構前のことで記憶が怪しいので、時間があればまとめようかと思います。

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