20200530のJavaScriptに関する記事は30件です。

GatsbyにDarkModeをつける

はじめに

本記事は https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-darkmode のクロスポスト記事になります。

この記事はGatsbyというヘッドレスCMS技術で構成されています。今回は「エンジニア初心者でもできる」を前提に以下の構成で記事を作成していこうと思います。

内容

DarkModeかっこいいですよね。私は大抵のアプリのテーマをDarkModeにして使用しています。みなさんはどうでしょうか?目にも良くて簡単におしゃれになるDarkModeをブログに取り入れてみましょう!

こんな内容になっています。

  • GatsbyのPluginで適用する
  • 素のCSSを使った方法
  • SCSSでも適用できる方法

GatsbyのPluginで適用する

これが一番手っ取り早いかもしれません。

gatsby-plugin-dark-modeをインストール

yarn add gatsby-plugin-dark-mode

// gatsby-config.js

module.exports = {
  plugins: ['gatsby-plugin-dark-mode'],
}

使い方

ThemeTogglerをImportしたコンポーネントを作成します。

import React from 'react'
import { ThemeToggler } from 'gatsby-plugin-dark-mode'

class MyComponent extends React.Component {
  render() {
    return (
      <ThemeToggler>
        {({ theme, toggleTheme }) => (
          <label>
            <input
              type="checkbox"
              onChange={e => toggleTheme(e.target.checked ? 'dark' : 'light')}
              checked={theme === 'dark'}
            />{' '}
            Dark mode
          </label>
        )}
      </ThemeToggler>
    )
  }
}

このPluginはLocalStorageでDarkかそれ以外かを判断しているみたいです。

global.cssを修正する

global.cssがない場合は、bodyタグでメインカラーを制御しているファイルを見つけて以下のように修正しましょう。

/* global.css */

body {
  --bg: white;
  --textNormal: #222;
  --textTitle: #222;
  --textLink: blue;
  --hr: hsla(0, 0%, 0%, 0.2);

  background-color: var(--bg);
}

body.dark {
  -webkit-font-smoothing: antialiased;

  --bg: darkslategray;
  --textNormal: rgba(255, 255, 255, 0.88);
  --textTitle: white;
  --textLink: yellow;
  --hr: hsla(0, 0%, 100%, 0.2);
}

Layout.jsを修正する

CSSまで適用させたらあとはその変数を使用するだけです。Layout.jsを変更しましょう。

class Layout extends React.Component {
  render() {
    return (
      <div
        style={{
          backgroundColor: 'var(--bg)',
          color: 'var(--textNormal)',
          transition: 'color 0.2s ease-out, background 0.2s ease-out',
        }}
      >
        ...
      </div>
    )
  }
}

素のCSSを使った方法

この方法は動的にCSSを変更できませんが、ユーザデバイスの設定に応じてテーマを変えることができます。

メリットとしては「素のCSSで構成されている, コストが低いのでとりあえず導入したい」になります。

cssの変更

まずは皆さんのプロジェクトのbodyタグもしくはroot:に設定されてるCSSを確認してください。

確認ができたら以下のCSSを加えていきましょう。

@media (prefers-color-scheme: light) {
  --theme-base: white;
  --theme-font: black;
  --theme-accent: red;
}

@media (prefers-color-scheme: dark) {
  --theme-base: black;
  --theme-font: white;
  --theme-accent: pink;
}

body {
  background-color: var(--them-base);
  color: var(--them-font);
}

prefers-color-scheme は CSS のメディア特性で、ユーザーがシステムに要求したカラーテーマが明色か暗色かを検出するために使用します。

とのことなので各デバイスの設定状況によってprefers-color-schemeの設定値が変わるということですね。ユーザデバイスに準拠するのでこのCSSだけでは手動で変更できませんが、最低限の対応としては良いと思います。

IEは。。。

やはりかというべきかIEは非対応とのことなのであしからず。。。というか無視しましょう!w

SCSSでも適用できる方法

先程のCSS変数を使用した応用になります。CSS変数をSCSS変数にラップしてあげればいいです。

// variables.scss
:root{
  @media (prefers-color-scheme: light) {
    --color-base: #222;
    --color-primary: #5D93FF;
    --color-secondary: #F7A046;
    --color_background-base: #FFF;
  }
  @media (prefers-color-scheme: dark) {
    --color-base: #FFF;
    --color-primary: #5D93FF;
    --color-secondary: #F7A046;
    --color_background-base: #282828;
  }
  @media (prefers-color-scheme: no-preference) {
    --color-base: #FFF;
    --color-primary: #5D93FF;
    --color-secondary: #F7A046;
    --color_background-base: #282828;
  }
}

$color-base: var(--color-base);
$color-primary: var(--color-primary);
$color-secondary: var(--color-secondary);
$color_background-base: var(--color_background-base);

やってみた

MacがDarkModeのとき

MacがLightModeのとき

まとめ

いかがでしたでしょうか。今回Gatsbyに限らず一般のCSSでも使用できる方法も合わせて紹介しました。状況に応じて必要なのを低コストで実現できると時間も取られないので良いですね!

一旦Gatsby特集は終わりますが、ちょいちょい自分でカスタマイズしていく予定なのでその度にご紹介できればと思います〜。それではまた。

参考

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

Kinx ライブラリ - Process

Kinx ライブラリ - Process

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。

今回は Process です。子プロセス起動とかやっぱり必要ですよね、ということで急遽こしらえました。

System.exec()

昔から標準で用意していたコマンド実行インタフェース。単純に C レベルでの system() を呼ぶだけなので手軽だが、終了するまで帰ってこないとか、標準出力を取得できないとか、色々不便ではある。ただし、シェル経由でコマンド実行するので、リダイレクトとかは使える。

で、今回はもっと色々とできる Process クラスを作ったので、そちらが今回の説明での本命。

Process

using Process

Process ライブラリは標準組み込みではないため、using ディレクティブを使用して明示的に読み込む。

using Process;

Exec

Process オブジェクトは new Process(command, opts) で作成する。引数はコマンド名と引数の配列、またはコマンド文字列。配列の場合は引数を別々に渡す感じで、コマンドライン文字列の場合は内部で解析して配列形式に自動で分解。

  • 配列: ["ls", "-1"] のような感じ。
  • 文字列: "ls -1" のような感じ。

作成されたプロセス・オブジェクトは以下のメソッドを持つ。

メソッド 概要
run() プロセスを開始させる。
launch() プロセスを開始させて切り離す。
std() 引数で渡されたオプションが返る。
{ in: opts.in, out: opts.out, err: opts.err }

この時点ではまだ実行していない。run() または launch() を行った時点で起動する。run() すると ProcessController クラスのオブジェクトが返る。

Run

run() することで ProcessController クラスのオブジェクトが返る。

var p = new Process(["cmd","arg1"]).run();

Launch

launch() は何も返さない(というか null が返る)。突き放して以後、子供の面倒は見ない、という方法。

new Process(["cmd","arg1"]).launch();

ProcessController

run() で返された ProcessController クラスは以下のメソッドを持つ。

メソッド 概要
isAlive() プロセスが生きている場合は true、既に終了している場合、または detach された後は false
wait() プロセスの終了を待ち、終了後にプロセスの終了コードを返す。detach 後は 0 を返す。
detach() プロセスを detach する

detach() はプロセス起動後に切り離す。Linux では launch() で切り離した場合と微妙に動作が違うがやりたいことは同じ。Windows で内部動作も同じ。

Linux ではプロセス起動時に切り離すためにいわゆる double-fork というやり方で切り離すが、これはプロセス起動時にしか使えない。プロセス起動後に切り離すのは実質的に不可能で、親プロセスではきちんと wait なり waitpid なりしてやらないと子はゾンビとなって生き残ってしまう。

そこで、detach() した瞬間に waitpid するためだけのスレッドを起動して、子が死ぬまで面倒見るようにしてある。

ちなみに double-fork とは Linux の、

  • 親プロセスが死ぬと子プロセスは init 配下になった上に init はひたすら wait しまくって面倒見てくれる

...という機能を利用して、一度 fork したプロセスからさらに fork した上で、最初に fork したプロセスを速攻終了させて孫プロセスの管理を init に任せる、というやり方です。

一番上の親プロセスは、最初に fork した子供の waitpid を忘れずに。勝手に面倒見てもらうのは孫のほうなので。

Wait

終了を待って終了コードを取得する例は以下の通り。

var p = new Process(["cmd", "arg1"]).run();
var status = p.wait();

detach していた場合は当然取得できない(0 が返る)。

Detach

先ほどから出てきている detach。プロセスは detach(切り離し)することもできる。切り離してしまえば子との縁は切れる。wait する必要もないし、終了を気にする必要もない。というか、気にしたくてもできなくなる。

var p = new Process(["cmd", "arg1"]).run();
p.detach();

Pipe

お待ちかねパイプ。Process を作った一番の目的はパイプ。子プロセスとの標準入出力をパイプに自由自在につないで情報のやり取りをしたい、というのが一番欲しい機能ですよね。

パイプの指定は、new Process(cmd, opts)opts で指定。パラメータは以下の 3 種類。

パラメータ 内容
in 標準入力を指定。
指定可能なものは、パイプ・オブジェクト、文字列、$stdin
out 標準出力を指定。
指定可能なものは、パイプ・オブジェクト、文字列、$stdout または $stderr
err 標準エラー出力を指定。
指定可能なものは、パイプ・オブジェクト、文字列、$stdout または $stderr
  • パイプ・オブジェクト ... パイプを使うためのオブジェクト。詳細は後述。
  • 文字列 ... ファイル名として、入力元、出力先ファイル。
  • $stdin$stdout$stderr ... 入力元、出力先を本プロセスの標準入出力にバインドする。

パイプ・オブジェクト

パイプ・オブジェクトは new Pipe() で作成する。[Read, Write] の 2 つのオブジェクトをペアで配列で返す。パイプオブジェクトには、以下のメソッドがある。

通常は Write パイプを子プロセスの out または err に指定して、Read パイプから読み込む。

Read Pipe

パイプのクローズは run() してからすること。run() するときに設定されるため。

メソッド
peek() パイプにデータがなければ 0、あれば 0 より大きい数値を返す。-1 はエラー。
read() パイプのデータを全て文字列として取得する。データがない場合は空文字列を返す。
close() パイプを閉じる。
Write Pipe

パイプのクローズは run() してからすること。run() するときに設定されるため。

メソッド
write(data) パイプにデータを書き込む。全て書き込めるとは限らず、書き込んだバイト数を返す。
close() パイプを閉じる。
サンプル

一般的な形として以下のように使う。

using Process;

var [r1, w1] = new Pipe();
var p1 = new Process([ "ls", "-1" ], { out: w1 }).run();
w1.close(); // もう使わないのでクローズしてよい
while (p1.isAlive() || r1.peek() > 0) {
    var buf = r1.read();
    if (buf.length() < 0) {
        System.println("Error...");
        return -1;
    } else if (buf.length() > 0) {
        System.print(buf);
    } else {
        // System.println("no input...");
    }
}
System.println("");

Write Pipe を親プロセス側で使う場合は、こんな感じ。

using Process;

// stdin はパイプから読み込み、標準出力に出力
[r1, w1] = new Pipe();
var p1 = new Process("cat", { in: r1, out: $stdout }).run();
r1.close(); // もう使わないのでクローズしてよい

// p1 の stdin に送り込む
var nwrite = w1.write("Message\n");
w1.close(); // パイプクロ―ズ、送信終了

p1.wait();

ちなみに、こうすると標準出力と標準エラー出力を制御できる。

new Process("cmd", { out: $stdout, err: $stdout }); // 標準エラー出力を標準出力に合流
new Process("cmd", { out: $stderr, err: $stderr }); // 標準出力を標準エラー出力に合流
new Process("cmd", { out: $stderr, err: $stdout }); // 入れ替え

Pipeline

パイプをつないでいくのは結構面倒(というか、どっちがどっちだっけ...? みたいな)な作業なので、一括して行ってくれる Process.pipeline というのも定義してみた。最後にコールバック関数を置いて、以下のように使う。

var r = Process.pipeline(cmd1, cmd2, cmd3, ..., &(i, o, pipeline) => {
    // i ... 最初のコマンドの stdin への書き込みパイプ
    // o ... 最後のコマンドの stdout からの読み込みパイプ
    // pipeline ... パイプライン・オブジェクト
    //    pipeline.input ....... 上記 i と同じ
    //    pipeline.output ...... 上記 o と同じ
    //    pipeline.peek() ...... pipeline.output.peek() と同じ
    //    pipeline.read() ...... pipeline.output.read() と同じ
    //    pipeline.write() ..... pipeline.input.write() と同じ
    //    pipeline.isAlive() ... パイプラインのいずれかのプロセスが生きていたら true
    //    pipeline.wait() ...... パイプラインの全てのプロセスが完了するのを待ち、
    //                           終了コードを配列で返す

    // コールバックの復帰値がそのまま Process.pipeline() の復帰値になる。
    return pipeline.wait();
});

コールバックしなくても使える。

var pipeline = Process.pipeline(cmd1, cmd2, cmd3, ...);
// pipeline ... パイプライン・オブジェクト
//  以下省略。

おわりに

子プロセス関係は Windows と Linux で違うので、そういうのを統一的に扱えるのはスクリプトの良いところ。ただし、コマンド自体は違ったりするので、そこはなかなか吸収できませんね。私は Windows ユーザーですが、UnxUtils を使って Unix コマンドをある程度コマンドプロンプトでも使えるようにしています。(Cygwin は環境を変えてしまうのであまり好きではない...)

ということで、ではまた次回。

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

Javascriptで小数を指定桁で切り捨て後に末尾を0埋めする方法

たとえば割合を計算して表示したい時に「98.75」「12.50」といった風に、小数点〇桁未満切り捨て&小数点を指定した桁数まで表示する方法を解説します。

やり方

結論から言うと、とりあえず下記のやり方で実現できます。
※変数valueが切り捨てたい値です。

// digits = 小数点第〇位まで表示するか(例:2)
const digits = 2;
const result = (Math.floor(value * Math.pow(10, digits)) / Math.pow(10, digits)).toFixed(digits);

ちょっと長くなったので、小数点第二位まで表示する場合のコードが以下になります。

const result = (Math.floor(value * 100) / 100).toFixed(2);

切り捨ての解説

「なぜそれで実現できるのか~」をちょこっと解説したいと思います。
まずは切り捨てについてですが、上で書いたコードで切り捨て処理にあたるのは以下の部分です。

// 小数点第二位未満を切り捨て
Math.floor(value * 100) / 100

100にあたる部分は、「小数点第〇位未満を切り捨て」の〇に入る数値分だけ10を累乗した値です。

そしてMath.floor()メソッドでは、与えられた引数の小数点以下を切り捨てて整数を返します。
たとえば5.6666だったら5、3.14だったら3を返します。

その上で、コードの流れを追ってみましょう。
例として、valueに12.3456が入っていて、小数点第二位未満で切り捨てるとします。
(1)12.3456 * 100 = 1234.56
(2)Math.floor(1234.56) = 1234
(3)1234 / 100 = 12.34

ようは表示したい部分を一度整数にしてから、また小数に戻す処理をしているんですね。
この方法はもちろん、切り捨てだけでなく、指定した桁数で四捨五入・切り上げしたい時にも使えます。

末尾を0埋めの解説

続いて末尾を0で埋めて、指定した桁数で表示する方法についてです。
ようは99の時も99.00みたいに返す方法ですね。こちらはあまり解説することはありません。

とりあえず末尾0埋めにあたる処理は以下の部分です。

// 小数点第二位まで表示
value.toFixed(2);

toFixed()メソッドでは、指定した桁数だけ小数点を表示して文字列で返してくれます。
ただこちらは切り捨ては行わないので、最初からtoFixed()メソッドだけを使って切り捨てまで実行しようとすると、想定通りの値が返ってこないので気を付けてください。

これで以上です。

参考

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

【別ページからの移動】固定ヘッダー分のアンカーリンクずれ解消

今まではjQueryで要素のoffset().topを取得したりしてましたが、ES6に自分のテンプレートを書き換えてる最中で改良できました。

移動先ページのURLにハッシュが含まれていれば、ヘッダー分移動してあげるというシンプルな作りです。

ミソはsetTimeOutで、これがないとうまくいきません。
headerHeightの値を取得する前にwindow.scrollByが終わってしまうということでしょうか。

window.addEventListener("DOMContentLoaded", () => {
  if (location.hash) {
    const headerHeight = document.getElementById("js-header").clientHeight;
    setTimeout(() => {
      window.scrollBy(0, - headerHeight);
    },100)
  }
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】別ページからアンカーリンク移動時の、固定ヘッダー分ずれ解消

今まではjQueryで要素のoffset().topを取得したりしてましたが、ES6に自分のテンプレートを書き換えてる最中で改良できました。

移動先ページのURLにハッシュが含まれていれば、ヘッダー分移動してあげるというシンプルな作りです。

ミソはsetTimeOutで、これがないとうまくいきません。
headerHeightの値を取得する前にwindow.scrollByが終わってしまうということでしょうか。

document.addEventListener("DOMContentLoaded", () => {
  if (location.hash) {
    const headerHeight = document.getElementById("js-header").clientHeight;
    setTimeout(() => {
      window.scrollBy(0, - headerHeight);
    },100)
  }
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptでchageイベントの取れないhidden項目やiframeの変化をMutationObserverで取得し処理する

HTMLのinput要素の値が書き換わった時に、何かの処理を行いたい場合、changeイベントで処理しますが、type属性がhiddenになっている場合、changeイベントが発火しないため、値が書き換わったら何かの処理を行うという事ができません。

iframeで読み込んでいるページの中身が変化したら処理を行いたいという場合もあるでしょう。
(私はiframeで読み込んでいるページに処理を追加できない構成でiframeの中身が変化したら親ページでの処理が必要でした)

これらは、MutationObserverを利用する事で解決します。

MutationObserver

MutationObserverはDOMが変化した時に処理を行うAPIです。
(APIと言っても何かを新たに読み込んだりする必要はありません)
Chrome, Edge, Firefox, Safari, IE11でも対応しているので安心です。

MDN MutationObserver

使い方

まずは、何が変化した時に処理を行うかを決めます。変化した時に処理を行いたい物をtrueにします。

  • childList
  • attributes
  • characterData
  • subtree
  • attributeOldValue
  • characterDataOldValue
  • attributeFilter

詳細:MutationObserverInit

実装

hidden項目の場合

今回は、Addボタンを押したらcounterの値が+1ずつ追加され、counterの数が5で割り切れる時に、Rankが1つ上がる。Rank上限は5
という処理を作ってみます。(実際はAddボタンを押した時に処理すればいいですが...)

<input type="hidden" name="counter" value="0">
<input type="button" name="add" value="Add">
<p>Rank: <input type="number" name="rank" value="1" readonly></p>

要素の定義

counter: クリックするたびに+1されるinputを定義
rank: ランクの値を表示するinputを定義

const counter = document.querySelector('[name=counter]');
const rank = document.querySelector('[name=rank]');

MutationObserverの設定

指定したDOM要素に変更があった時に行う処理です。
mutationsには、変更されたDOM要素が配列で入って来るためループし、変更された属性(attributeName)がvalueの時がinputの値が変わった時なので、続きの処理を行っています。

const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
        rank.value = Math.floor(parseInt(mutation.target.value, 10) / 5) + 1;
        if (rank.value == 5) observer.disconnect();
    });
});

rankの最大値を5にしているので、もしもrankの値が5に変わったら、DOMの監視を終了させています。
DOMの監視が不要になったら終了しましょう。

observer.disconnect();

このconfigでtrueを指定した物が、監視対象となります。
今回はvalue値の変化だけを監視するので、属性の変化を監視するattributesをtrueにし、attributeFilterにて監視るる属性をvalueに限定させています。

監視対象をなるべく絞り込んで置く事で

const config = {
    attributes: true,
    attributeFilter: ['value']
};

そして、監視を開始します。

observer.observe(counter, config);

以上をまとめた物がこちらです。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8" />
        <title>Rank</title>
    </head>
    <body>
        <input type="hidden" name="counter" value="0">
        <input type="button" name="add" value="Add">
        <p>Rank: <input type="number" name="rank" value="1" readonly></p>
        <script>
            (() => {
                'use strict';
                const counter = document.querySelector('[name=counter]');
                const rank = document.querySelector('[name=rank]');

                const observer = new MutationObserver((mutations) => {
                    mutations.forEach((mutation) => {
                        rank.value = Math.floor(parseInt(mutation.target.value, 10) / 5) + 1;
                        if (rank.value == 5) observer.disconnect();
                    });
                });
                const config = {
                    attributes: true,
                    attributeFilter: ['value']
                };
                observer.observe(counter, config);

                document.querySelector('[name=add]').addEventListener('click', () => {
                    counter.value = parseInt(counter.value, 10) + 1;
                });
            })();
        </script>
    </body>
</html>

valueの値が書き換わっていない時

document.querySelector('[name=hoge]').value = "fuga";

もしも、[name=hoge]の値変化を監視していて、上記のように値をセットした時、
画面上ではinputの値が変化しているのに、DOMが書き換わっておらず、MutationObserverで変更処理が走らない場合があります。

その時は、このようにsetAttributeを使う事でDOMが変化し、変更処理が走るようになります。

document.querySelector('[name=hgoe]').setAttribute('value', 'fuga');

iframeの中身の変化

iframeの中身が変わった時の処理はjQueryを利用すると楽に実装できます。
iframeのbodyの中身が変化した時の処理ですが、iframeの読み込みが終わるのをsetIntervalで待ってから、処理を行っています。

<iframe src="#" id="frame"></iframe>
<script>
    const frameLoading = () => {
            setInterval(() => {
            if ($('#frame').contents().find('body') != 'undefined') {
                clearInterval(frameLoading);
                const target = $('#frame').contents().find('body');
                const config = {
                    attributes: true,
                    characterData: true,
                    childList: true,
                    subtree: true
                };
                new MutationObserver((mutations) => {
                    // do something.
                    console.log(mutations);
                }).observe(target, config);
            }
        }, 100);
    };
    frameLoading();
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TECH CAMP 7,8週目

TECH CAMPは7週目より最終課題に入っています。最終課題はメルカリのクローンフリマアプリのチーム開発。ちなみに私がチームのスクラムマスターで、5名で開発を推進してます。ひとつひとつのタスクの工数がきちんと把握できていない中での調整は難しいですが、今のところバランス良く進められているかなと感じています。
進め方としては、全員でデータベース設計をし、完了後にまず全てのページの表示に必要なルーティング、コントローラー、ビューを用意、そこからそれぞれのページのマークアップを全員に振り分けます(1〜2ページ/人)。その後、サーバーサイドを全員に振り分けます。これにより全員が様々なページ/機能に触れられるかなと考えたんですが、タスクによっては時間がかかるものがあり、そのメンバーはひとつの機能実装の深堀ばっかりになってしまい、そこは少し申し訳なかったなと思います。

 【担当した実装】
・商品購入確認ページのマークアップ
・商品詳細ページのマークアップ
・ウィザード形式を用いたユーザー新規登録、ログイン、ログアウト機能
・カテゴリ表示、選択機能

スクラムマスターは指示出しをしつつ、もちろん開発も行います。この中でも難しく感じたカテゴリ機能実装を以下に残しておきます。

・カテゴリ表示、選択機能
まずは商品詳細ページへの表示です。
スクリーンショット 2020-05-30 18.10.12.png
このように、3階層のカテゴリの表示を行います。例えば、”レディース”の中の”トップス”の中の”Tシャツ”のような感じです。データベース設計時に3つのテーブルが必要では?との案もありましたが、調べてみると、以下のgemを使用する事でひとつのテーブルで可能ということがわかりました。

gem 'ancestry'

bundle installし、has_ancestryとモデルに記述。

app/models/category.rb
class Category < ApplicationRecord
  has_many :items
  has_ancestry
end

さて、データベースに大量のカテゴリ情報をどう入力するのか‥。ということで調べるとCSVファイルを読み込ませる方法が。まずはヘッダーに全カテゴリが入力済みだったので(メンバーよ、ありがとう!)これをスプレットシートにコピペし、関数でカテゴリ名のみ抜き出しました。そして下記ファイルにこのように記述。

db/seeds.rb
require "csv"

CSV.foreach('db/category.csv', headers: true) do |row|
  Category.create(
    name: row['name'],
    ancestry: row['ancestry']
  )

end

dbファイル下にCSVファイルを移動し、ターミナルで$rake db:seedを行うとデータベースに読み込まれます。
スクリーンショット 2020-05-30 18.26.48.png
こんな感じです。親カテゴリのancestryカラムはnullで、その子カテゴリのancestryカラムには親カテゴリのidが入ります。その孫カテゴリのancestryカラムには親カテゴリのid/子カテゴリのidが入ります。
そして表示させるためにコントローラーのshowアクションにこのように記述。こうする事でカテゴリテーブルの親、子、孫を呼び出せるそう(parentやchildで)。なんて便利なgemなんだ。

app/controllers/items_controller.rb
def show
  @items = Item.find(params[:id])
  @grandchild = Category.find(@items.category_id)
  @child = @grandchild.parent
  @parent = @child.parent
end

そしてビューを編集。これで表示は完了です。

app/views/items/show.html.haml
%th カテゴリー
  %td
    = @parent.name
    %br
    = @child.name
    %br
    = @grandchild.name

次は商品出品時のカテゴリ選択機能です。(苦手な)ajaxを使った動的な実装です。
こんな感じ。
12657385b75fb9fce59088cb8dc78ceb.gif

まずはルーティングでアクション先を指定。

config/routes.rb
resources :items do 
  collection do
     get 'category/get_category_children', to: 'items#get_category_children', defaults: { format: 'json' }
     get 'category/get_category_grandchildren', to: 'items#get_category_grandchildren', defaults: { format: 'json' }
   end
 end

コントローラーへ記述。

app/controllers/items_controller.rb
def new 
  @category = Category.where(ancestry: "").limit(13)
end

def get_category_children  
  @category_children = Category.find(params[:parent_id]).children 
end

def get_category_grandchildren
  @category_grandchildren = Category.find(params[:child_id]).children
end

jbuilderファイルを作成。

app/views/items/get_category_children.json.jbuilder
json.array! @category_children do |child|
  json.id child.id
  json.name child.name
end
app/views/items/get_category_grandchildren.json.jbuilder
json.array! @category_grandchildren do |grandchild|
  json.id grandchild.id
  json.name grandchild.name
end

ビューを編集。

app/views/items/new.html.haml
.status_register
  = form_with(model: @item, local: true) do |form|
    .status_register__status_category_group
      .status_register__status_category_group__category
        .status_register__status_category_group__category__register_title
          カテゴリー
        .status_register__status_category_group__category__choose
          = form.collection_select :category_id, @category, :id, :name,{prompt: '---'}, {id: 'parent_category'}

最後にjsファイルを作成します。親カテゴリ選択後に子カテゴリのセレクトボックスが出現、がなかなかうまくいかず時間がかかりました。コンソールで見るとイベント発火が確認出来ていたので、単純にhtmlのところかなと思いますが、かなりいじったのできちんとした原因がわからず‥。しまった‥。
他のメンバー&自分のためにコメントアウト残してますが、そのまま貼ります。

app/assets/javascripts/category.js
//この1行目の記述でリロード時に動作。カリキュラムでは削除していたturbolinks関連の記述を削除しないよう注意
$(document).on('turbolinks:load', function(){
  $(function(){
    //オプション設定
    function appendOption(category){
      var html = `<option value="${category.id}" data-category="${category.id}">${category.name}</option>`;
      return html;
    }
    //子カテゴリー表示(items/new.html.hamlのカテゴリー選択部分を編集した場合は要確認)
    function appendChidrenBox(insertHTML){
      var childSelectHtml = '';
      childSelectHtml = `<div class='status_register__status_category_groupl__category__choose__added' id= 'children_wrapper'>
                          <div class='status_register__status_category_group__category__choose1'>
                            <i class='fas fa-chevron-down status_register__status_category_group__category__choose--arrow-down'></i>
                            <select class="status_register__status_category_group__category__choose--select" id="child_category" name="item[category_id]">
                              <option value="---" data-category="---">---</option>
                              ${insertHTML}
                            <select>
                          </div>
                        </div>`;
      $('.status_register__status_category_group__category__choose').append(childSelectHtml);
    }
    //孫カテゴリー表示(items/new.html.hamlのカテゴリー選択部分を編集した場合は要確認)
    function appendGrandchidrenBox(insertHTML){
      var grandchildSelectHtml = '';
      grandchildSelectHtml = `<div class='status_register__status_category_group__category__choose__added' id= 'grandchildren_wrapper'>
                                <div class='status_register__status_category_group__category__choose2'>
                                  <i class='fas fa-chevron-down status_register__status_category_group__category__choose--arrow-down'></i>
                                  <select class="status_register__status_category_group__category__choose__box--select" id="grandchild_category" name="item[category_id]">
                                    <option value="---" data-category="---">---</option>
                                    ${insertHTML}
                                  </select>
                                </div>
                              </div>`;
      $('.status_register__status_category_group__category__choose').append(grandchildSelectHtml);
    }
    //親カテゴリー選択後イベント発火
    $('#parent_category').on('change', function(){
      //選択された親カテゴリーのidを取得
      var parent_category_id = document.getElementById
      ('parent_category').value;
      $.ajax({
        url: '/items/category/get_category_children',
        type: 'GET',
        data: { parent_id: parent_category_id },
        dataType: 'json'
      })
      .done(function(children){
        //親カテゴリが変更された時に子・孫カテゴリを削除する
        $('#children_wrapper').remove();
        $('#grandchildren_wrapper').remove();
        var insertHTML = '';
        children.forEach(function(child){
          insertHTML += appendOption(child);
        });
        appendChidrenBox(insertHTML);
      })
      //エラー警告
      .fail(function(){
        alert('再度カテゴリーを選択してください');
      })
    });
    //子カテゴリー選択後イベント発火
    $('.status_register__status_category_group__category').on('change','#child_category', function(){
      //選択された子カテゴリーのidを取得
      var child_category_id = $('#child_category option:selected').data('category');
      $.ajax({
        url: '/items/category/get_category_grandchildren',
        type: 'GET',
        data: { child_id: child_category_id },
        dataType: 'json'
      })
      .done(function(grandchildren){
        if (grandchildren.length != 0) {
          //子カテゴリが変更された時に孫カテゴリを削除する
          $('#grandchildren_wrapper').remove();
          var insertHTML = '';
          grandchildren.forEach(function(grandchild){
            insertHTML += appendOption(grandchild);
          });
          appendGrandchidrenBox(insertHTML);
        }
      })
      //エラー警告
      .fail(function(){
        alert('再度カテゴリーを選択してください');
      })
    });
  });
});

これにて完了です。
参考記事があり大変助かりました。
データベース設計時にこのancestryを使用する方法に辿り着いて良かったです。みんなで検索して見つけたのかな?最初に親テーブル、子テーブル、孫テーブルがそれぞれ必要だ!と主張していたのは自分でしたが(笑)

参考

Rails5でjqueryを動かす方法
多階層カテゴリでancestryを使ったら便利すぎた
多階層セレクトボックスの実装
f.collection_selectについて
【Rails】rake seedコマンドでCSVファイルからDBに読み込ませる方法

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

はじめてのThree.js 8章 日記

はじめてのthree.js

新たに知った知識

  • group化で2つ以上のオブジェクトを一括でコントロールできる(これが一番知りたかった)
  • mergeするか否かでパフォーマンスが変わってくる
  • json保存するときにlocalstrageAPを使って保存
  • collada(.dae)はジオメトリもマテリアルも定義できる

気づいたこと

  • blender2.8の神アップデートによりデフォルトでthree.jsで表示できるエクスポート形式が搭載されたためaddonをわざわざ入れる必要がなくなった 現在githubではglTFをサポートしているためremoveされている模様。どうしても本(筆者は第2版)通りにやりたい場合はcommit戻るべし 参考 Screen Shot 2020-05-30 at 7.39.17.png
  • blender無料はやっぱりおかしい(褒め言葉)

wow moment

  • blenderからの読み込みが一番やりたかった事!神!!!
  • ポイントクラウドかっこいい Screen Shot 2020-05-30 at 21.00.08.png

まだ解決していない点

9章はこちら

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

Djangoを用いてhtmlからPythonファイルを実行する

ここで書くこと

この記事では「Djangoを用いてWebページからサーバ上に用意したPythonファイルを実行し、htmlから渡したデータをcsvファイルに出力する方法」について書いています。

また、執筆環境は
OS:macOS Catalina バージョン 10.15.4
Python:3.7.6
Django:3.0.3
となっています。

Djangoについて

前回の記事にてDjangoのインストール→htmlの表示までの流れについて書かせていただきましたので、Djangoの導入等についてはそちらをご参考ください。

Pythonファイルの準備

Pythonファイルの作成

まず、実行したいPythonファイルをDjangoのサーバ上に用意します。説明のために今回は以下のようなPythonファイルを用意します。

write_data.py
# coding:utf-8
import os
import csv

# htmlからのデータをcsvファイルに記録
def write_csv(data):
    datas = [data]
    with open(os.getcwd()+'/myapp/application/'+'data.csv','a') as f:
        writer = csv.writer(f, lineterminator='\n')
        writer.writerow(datas)

これにより、write_csv()の引数にデータを渡して呼び出すことでdata.csvにそのデータが書き込まれます。なおここでは<アプリ名>/applicationフォルダ内にPythonファイルを配置&csvファイルを出力することを想定しているためos.getcwd()+'/myapp/application/'+'data.csv'としていますが、この部分は環境に応じて適宜読み替えてください。

Pythonファイルの配置

用意したPythonファイルをDjangoのサーバ上に配置します。Pythonファイルを<アプリ名>/applicationフォルダ内に置く場合、アプリ内のディレクトリは以下のようになるかと思います。

<プロジェクト名>
- db.sqlite3
- manage.py
- <プロジェクト名>
  - __init__.py
  - asgi.py
  - settings.py
  - urls.py
  - wsgi.py
  - __pycashe__
    - (.pycファイルが複数)
- <アプリ名>
  - __init__.py
  - admin.py
  - apps.py
  - models.py
  - tests.py
  - urls.py
  - views.py
  - migrations
    - __init__.py
  - application  # 作成したフォルダ
    - write_data.py  # 用意したPythonファイル
- templates
- static

もちろん、ここに置かないといけないという訳ではないのでファイルの配置場所は任意の場所で大丈夫です。

htmlからPythonファイルを実行できるようにする

ファイルの用意と配置が完了しましたら、実際にhtmlからそのPythonファイルを実行させてcsvファイルを作成してみます。その前に、データの流れがどのようになっているかを把握してから実装した方が作業しやすいと思いますので、今回想定するデータの流れを書いてみたいと思います。
スクリーンショット 2020-05-30 19.04.32.png
パワポで作ってみました、ガサツな図で申し訳ありません、、
簡略化の為にプログラムと異なる部分も多々ありますが、csvファイルへの書き込みまでのデータの流れとしてはこんな感じです。何となくでもイメージが伝われば嬉しいです。

html(index.html)
 →myapp/views.pyのcall_write_data()にデータを送信
 →call_write_data()内でapplication/write_data.pyのwrite_csv()メソッドを実行
 →それにより渡したデータがcsvファイルに書き込まれる

言葉で表すとこんな感じです。これを踏まえ、次から実際にhtmlからPythonファイルを実行できるよう各ファイルを編集していきます。

html:ajaxを用いてviews.pyにデータを送信する

views.py内のcall_write_data()メソッドにデータを渡す為に、html上でajaxを用いてデータを送信してみたいと思います。ここに関しては色々な方法があるかと思いますので、アプリケーションに合う方法を用いていただければと思います。

index.html
{% load static %}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>HTML</title>
    <link rel='stylesheet' type='text/css' href="{% static 'style.css' %}"/>
    <script type="text/javascript" src="{% static 'script.js' %}"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  </head>

  <body>
      <span>文字を入力した後にボタンを押してください</span>
      <br>
      <input type="text" size="50" id="input_form">
      <button type="button" onclick="clickBtn()">送信</button>

      <script>
        function clickBtn() {
          var txt = document.getElementById("input_form").value;

          $.ajax({
            url: "{% url 'myapp:call_write_data' %}",
            method: 'GET',
            data: {"input_data": txt},
            dataType: "text",
            contentType: "application/json",
            beforeSend: function(xhr, settings) {
              if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrf_token);
              }
            },
            error: function(xhr, status, error) {
              console.log("error")
            }
          })
          .done(function(data) {
            console.log("Success"); 
          });

          // csrf_tokenの取得に使う
          function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
              var cookies = document.cookie.split(';');
              for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                  break;
                }
              }
            }
            return cookieValue;
          }

          // ヘッダにcsrf_tokenを付与する関数
          function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
          };
        }

      </script>
  </body>
</html>

送信ボタンを押すとclickBtn()が実行され、ajaxによりデータが送信されるようになっています。urlの部分はmyapp: call_write_dataとなっており、これによりviews.pyに記述したcall_write_data()というメソッドにデータが送信されます。

データ部分はdata: {"input_data": txt}となっており、データを受け取る側では"input_data"と指定することで目的のデータを取得することができます。ここでは一つだけにしていますが、data: {"data1": txt1, "data2": txt2}のようにデータの個数や型などデータの形式については自由に設定できます。

myapp/views.py:実行したいPythonファイルとメソッドを指定して実行する

myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
# application/write_data.pyをインポートする
from .application import write_data

# Create your views here.
def index(req):
    return render(req, 'index.html')

# ajaxでurl指定したメソッド
def call_write_data(req):
    if req.method == 'GET':
        # write_data.pyのwrite_csv()メソッドを呼び出す。
        # ajaxで送信したデータのうち"input_data"を指定して取得する。
        write_data.write_csv(req.GET.get("input_data"))
        return HttpResponse()

ここではajaxで送信されたデータを取得し、実行したいPythonファイルのメソッドにそのデータを渡して呼び出しています。

myapp/urls.py:htmlからviews.pyのcall_write_data()にデータを送信できるようにする

myapp/urls.py
from django.urls import path
from . import views

app_name = 'myapp'
urlpatterns = [
    path(r'', views.index, name='index'),
    # 以下を追記(views.pyのcall_write_data()にデータを送信できるようにする)
    path("ajax/", views.call_write_data, name="call_write_data"),
]

パスを通すことで、html上からajax通信を用いてviews.pyの指定したメソッドにデータを送信できるようになります。

実際にcsvファイルに書き込まれるかを確認する

以上で必要な編集は完了です。

$ python manage.py runserver

でサーバを立ち上げ、表示されたアドレスにアクセス(htmlを表示)し入力フォームに適当な文字を入力してから送信ボタンを押してみてください。

- <アプリ名>
  - __init__.py
  ...
  - application
    - write_data.py
    - data.csv  # 生成されたcsvファイル

このようにアプリ内に作成したapplicationフォルダの中にcsvファイルが生成され、入力した文字列がファイルに記録されていれば問題なくデータが送信されている&Pythonファイルが実行されています。

補足

今回はhtmlからPythonファイルを実行し、送信されたデータをcsvファイルに書き込む方法についてご説明しましたがその逆も可能です。

説明の簡略化の為に、”write_data.pyから渡されたデータをviews.pyで取得しそれをhtmlに渡して表示する”ことを行ってみたいと思います。変更箇所のあるファイルだけ以下に載せていきます。

myapp/application/write_data.py

return_text()というメソッドを追記する。

myapp/application/write_data.py
   # coding:utf-8

   import os
   import csv

   # htmlからのデータをcsvファイルに記録
   def write_csv(data):
       datas = [data]
       with open(os.getcwd()+'/myapp/application/'+'data.csv','a') as f:
           writer = csv.writer(f, lineterminator='\n')
           writer.writerow(datas)

   # 以下を追記(return_text()を呼び出すと"Hello!!"が返される)        
+  def return_text():
+      return "Hello!!"

myapp/views.py

write_data.pyにて追記したreturn_text()を呼び出し、返ってきた文字列を取得する(dataに格納する)。そのデータをHttpResponse()を用いてhtmlに渡す。

myapp/views.py
  from django.shortcuts import render
  from django.http import HttpResponse
  # application/write_data.pyをインポートする
  from .application import write_data

  # Create your views here.
  def index(req):
      return render(req, 'index.html')

  # ajaxでurl指定したメソッド
  def call_write_data(req):
      if req.method == 'GET':
          # write_data.pyのwrite_csvメソッドを呼び出す。
          # ajaxで送信したデータのうち"input_data"を指定して取得する。
          write_data.write_csv(req.GET.get("input_data"))

          # write_data.pyの中に新たに記述したメソッド(return_text())を呼び出す。
+         data = write_data.return_text()
          # 受け取ったデータをhtmlに渡す。
+         return HttpResponse(data)

index.html

ajax通信が成功するとHttpResponse()で渡した引数が.done(function(data) {の部分に渡されるので、そのデータをページに表示する。

index.html
  {% load static %}
  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <title>HTML</title>
      <link rel='stylesheet' type='text/css' href="{% static 'style.css' %}"/>
      <script type="text/javascript" src="{% static 'script.js' %}"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    </head>

    <body>
      <span>文字を入力した後にボタンを押してください</span>
      <br>
      <input type="text" size="50" id="input_form">
      <button type="button" onclick="clickBtn()">送信</button>

      <!-- views.pyから渡された文字列を表示する。 -->
+     <br>
+     <span id="text"></span>

      <script>
        function clickBtn() {
          var txt = document.getElementById("input_form").value;

          $.ajax({
            url: "{% url 'myapp:call_write_data' %}",
            method: 'GET',
            data: {"input_data": txt},
            dataType: "text",
            contentType: "application/json",
            beforeSend: function(xhr, settings) {
              if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrf_token);
              }
            },
            error: function(xhr, status, error) {
              console.log("error")
            }
          })
          .done(function(data) {
            // views.pyのcall_write_data()にてreturnしたHttpResponse(data)のデータはここで取得できる。
            // フォームの下部に追記したspan部分の内容を書き換える。
+           document.getElementById("text").textContent = data;
            console.log("Success"); 
          });

          // csrf_tokenの取得に使う
          function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
              var cookies = document.cookie.split(';');
              for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                  break;
                }
              }
            }
            return cookieValue;
          }

          // ヘッダにcsrf_tokenを付与する関数
          function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
          };
        }

      </script>
    </body>
  </html>

これにより、送信ボタンを押して正しくPythonファイルが実行されると"Hello!!"という文字列がhtmlに渡され、入力フォームの下の部分に渡されたHello!!という文字列が表示されると思います。

これを応用することで、サーバ上のPythonファイルを実行しサーバ上のファイルのデータを読み書きしたり、そのデータをhtml上に反映させることが可能になります。

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

javascript

初期値
'use strict'

要素の作成・追加(セット)
//要素の作成(追加とセット)
let div = document.createElement('div');

//要素の追加 例)document.bodyにdivを追加
document.body.appendChild(div);
)要素名(element)divを追加
element.appendChild(div);

//classの追加
要素.classList.add('クラス名');
付け外し「toggle」
//classの付け外し
要素.classList.toggle('クラス名');
イベント
要素.addEventListener(イベント, 関数, オプション);

//イベント
load//Webページの読み込みが完了した時に発動(画像などのリソースすべて含む)
DOMContentLoaded//Webページが読み込みが完了した時に発動(画像などのリソースは含まない)
click//マウスボタンをクリックした時に発動
mousedown//マウスボタンを押している時に発動
mouseup//マウスボタンを離したときに発動
mousemove//マウスカーソルが移動した時に発動
keydown//キーボードのキーを押したときに発動
keyup//キーボードのキーを離したときに発動
keypress//キーボードのキーを押している時に発動
change//フォーム部品の状態が変更された時に発動
submmit//フォームのsubmitボタンを押したときに発動
scroll//画面がスクロールした時に発動
文字
要素.innerText = 関数 or "文字"
要素.textContent = 関数 or "文字"
要素.innerHTML = 関数 or "文字"

理解できていない点

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

Javascriptでアコーディオンメニュー作ってみた

はじめに

最近練習中のJavascriptで、アコーディオンメニューを実装してみました。

使い方

jsファイルを読み込むと、指定した規則に従ってアコーディオンメニューの設定をするようなファイルです。
そこまでパターンを試してみたわけではないですが、複数階層でも一応対応できると思います。
ソースは以下のGitリポジトリにあげてみました。
https://github.com/takku66/JsAcdMenu

動作例

レイアウトや色などは少し手を抜いてしまっていますが、ご了承下さい。。。
以下のように動作します。
sample.gif

最初はJavascriptで、heightを変えていたのですが、CSSで高さを変えるような仕様に変更しました。

補足

作成にあたっては、下記を参考にさせていただきました。
https://ao-system.net/javascript/code/1002/

今はReactやVueがあるので、このようなアコーディオンメニューの実装は比較的簡単にできるようですね。まだJavascriptのフレームワークについては勉強しきれていないですが、時間がある時に勉強してみようと思います!

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

Nuxt.jsとFirebaseで個人開発アプリをシェアできるウェブサービスを作った話

はじめに

4月で2年目になったウェブエンジニアです。普段はFEとBEを3:7くらいの割合でコードを書いています。
1年目は仕事で使っている技術やノウハウを学ぶことで精一杯でしたが、最近は仕事をこなしていく中で興味を持った技術について勉強する余裕が出てきました。
そこで、1年間ウェブエンジニアとして仕事をし、学んだ知識を生かして自分で1からウェブサービスを作ってみようと思い立ちました。
この記事では、備忘録という意味合いを兼ねつつ、なぜ、どのようにして、どんなものを作ったのかを書いていこうと思います。

作ったサービス

アプリガーデンという個人開発アプリをシェアできるサービスを作りました。
Twitter、Google、Githubのいずれかのアカウントがあれば簡単にユーザー登録ができ、アプリを投稿することができます。
また、ユーザー登録をせずに他の方々が作ったアプリを見るだけということも可能です。
スクリーンショット 2020-05-28 20.24.13.png

昨今、プログラミングへの関心が高まり、学びやすい環境になり個人でアプリケーションを開発するハードルが下がったと感じています。
アプリが完成したら公開してできるだけ多くの人に使ってほしいと思うのがエンジニアの性ですが、宣伝という壁が立ちはだかります。
法人であれば広告を出稿したりプレス掲載したりという方法を取ることができますが個人で行うとなると難しいのではないかと思います。
そんな個人開発エンジニアの方々が気軽に、簡単に作ったアプリをシェアできる場を作りたいなと思ったのがアプリガーデンを開発したきっかけです。
アプリをシェアすることはもちろん、ユーザー登録をせずにアプリを見るだけということも可能で、他のエンジニアが作ったアプリを見てモチベーションをもらうという使い方もできます。

システムアーキテクチャ

architecture.001.png

フロントエンドにNuxt.js、バックエンドにFirebase、CI/CDにはGitHub Actionsを利用しています。
UIフレームワークにはVuetifyを採用しています。コンポーネントが豊富で日本語のドキュメントが充実しているのでとても使いやすいです。
バックエンドはできるだけコードを書く量を減らしたかったので全てFirebaseでまかなっています。ウェブコンソールで1つプロジェクトを作ることで様々なプラットフォームを利用できるので管理も楽になります。
今回はSNS認証のためにAuthentication、アプリ情報やユーザー情報を保存するためにFirestore、ウェブサイトとして公開するためにHostingを利用しています。独自ドメインへの接続も含めて全て無料プランで利用することができます。

Firebase Authentication
Firebase Firestore
Firebase Hosting

Firebase HostingへのデプロイはGitHub Actionsで自動化しています。
releaseブランチへのコミットがトリガーとなりビルドからデプロイまで自動で行われるようにしています。

GitHub Actions

おわりに
実装期間は主に土日に作業をして1ヶ月ほどでした。
Firebaseのおかげでバックエンドはウェブコンソールからぽちぽちするだけで環境を作ることができるので楽をすることができました。
今のところ最低限の機能しか実装できていないのでより使いやすくなるように拡張をしていきたいと思います。

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

Node.js インストールからREPL、ファイル実行まで(mac)

インストール

nodejs
nodejsのサイトでDLしちゃいましょう。
ターミナルでやる方法もありますが、別でググって下さい。

% node -v
v13.8.0

インストールできたらターミナルで確認。npmも一緒にインストールされます。
npmはpythonでいうpipです。

 npm -v
6.14.5

Nodejs REPL

$ node
> 
> 3 + 2 
6
>

nodeシェルをうまく使うと開発にも役立ちます。

便利なREPLコマンド

.break セッション内でブロックから抜け出す
.clear セッション内でブロックから抜け出す
.editor 複数行のコードを書ける
.exit セッション終了
.help ヒントの表示
.load ローカルなファイルにアクセスする
.save セッションのコードをファイルに保存する

Node.jsでファイルを実行する

nodeの後にファイル名を指定することでJavascriptファイルをNode.jsで実行します

main.js

let message = "Hi, This is Node.js.";
console.log(message);

terminalで実行

% node main.js
> Hi, This is Node.js.

こんな感じです。

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

組込み向けJSインタプリタEspruinoをRaspberryPi上で試す

組込みCPUでも動作するJavaScriptインタプリタEspruinoを触ってみます。
ECMAScriptにどれくらい対応してるのか実際に動かしてチェックしてみます。

Espruino公式ではEspruino対応しているボード等色々あります
http://www.espruino.com/

前半はRPiターゲットにコンパイルしてES対応チェック
後半で組込みCPUターゲットにコンパイルしてバイナリサイズを確認してみます。

Espruino version : 2v05.118

RPiターゲットでコンパイル

RPiターゲットでコンパイルします

$ git clone git@github.com:espruino/Espruino.git
$ cd Espruino
$ BOARD=RASPBERRYPI make

ファイルサイズは3.4MBくらいになりました。RPi上で実行してみます。

$ ./espruino
espruino: Interactive mode.
espruino: Size of JsVar is now 28 bytes
espruino: Size of JsVarRef is now 4 bytes
espruino: Added SIGINT hook
espruino: Added SIGHUP hook
espruino: Added SIGTERM hook

 ____                 _
|  __|___ ___ ___ _ _|_|___ ___
|  __|_ -| . |  _| | | |   | . |
|____|___|  _|_| |___|_|_|_|___|
         |_| espruino.com
 2v05.118 (c) 2019 G.Williams

Espruino is Open Source. Our work is supported
only by sales of official boards and donations:
http://espruino.com/Donate

>

問題なく動作

ESの対応状況

Espruino公式が出しているES対応表はこちら
http://www.espruino.com/Features

基本的にはES5、ES6の一部対応してるようす

アロー関数

アロー関数、 const 、let は問題なくいけます

hello.js
const hello = () => { console.log('hello'); }
let world = () => { console.log('world'); }

hello();
world();
$ ./espruino hello.js

 ____                 _
|  __|___ ___ ___ _ _|_|___ ___
|  __|_ -| . |  _| | | |   | . |
|____|___|  _|_| |___|_|_|_|___|
         |_| espruino.com
 2v05.118 (c) 2019 G.Williams

Espruino is Open Source. Our work is supported
only by sales of official boards and donations:
http://espruino.com/Donate

hello
world

※実行すると毎回キャプションが出ますが以下カット

Promise

Promiseも対応OK
finallyはES9仕様ですがついでに対応してないかなと確認してみたところ、finallyは未対応でした

promise.js
function pr(err) {
  return new Promise((resolve, reject) => {
    if(err) { reject(new Error('!!!error!!!')); }
    else { resolve('hello'); }
  });
}

pr(false)
  .then( res => { console.log(res); } )
  .catch( e => { console.log('error : '+e.message); } );

pr(true)
  .then( res => { console.log(res); } )
  .catch( e => { console.log('error : '+e.message); } );
$ ./espruino promise.js
hello
error : !!!error!!!

async/await

async/awaitは未対応

async.js
function pr(err) {
  return new Promise((resolve, reject) => {
    if(err) { reject(new Error('!!!error!!!')); }
    else { resolve('hello'); }
  });
}

async function foo() {
  const ret = await pr(false);
  console.log(ret);
}

foo();
$ ./espruino async.js
Uncaught ReferenceError: "async" is not defined

class

classは対応OK

cl.js
class cl {
  constructor() {
    console.log('cl constructor');
  }

  pr(err) {
    return new Promise((resolve, reject) => {
      if(err) { reject(new Error('!!!error!!!')); }
      else { resolve('hello'); }
    });
  }
}

const c = new cl();

c.pr(false)
  .then( res => { console.log(res); } );
$ ./espruino cl.js
cl constructor
hello

EventEmitter

Node.jsのノリで require('events') すると、Espruinoでは「そんなモジュールない!」と怒られます

emit.js
const EventEmitter = require('events');
const em = new EventEmitter();

em.on('foo', (val) => { console.log('hello', val); });
em.emit('foo', 'world');
$ ./espruino emit.js
Uncaught Error: Module events not found

公式を調べてみると、on/emitはObjectクラスの組込み関数として扱われている様子
https://www.espruino.com/Reference#Object

emit-espruino.js
Object.on('foo', (val) => { console.log('hello', val); });
Object.emit('foo', 'world');
$ ./espruino emit-espruino.js
hello world

requireでファイルロード

組込み系は基本的にはファイルシステムの概念がないので
Node.jsのようにrequireで自前のファイルをロードするという方法はEspruinoでは取っておらず
基本的に1ファイルにフラット化して実行しましょうというスタンスらしい

例えば以下2ファイルを用意したとき

exp.js
class exp {
  constructor() {
    console.log('exp constructor');
  }

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

module.exports = exp;
req.js
const exp = require('./exp');
const e = new exp();
e.hello();

Node.jsでは問題なく実行できるが

Node.js
$ node req.js
exp constructor
hello

espruinoではexpファイルなんてものはないと怒られる

espruino
$ ./espruino req.js
Uncaught Error: Module ./exp not found

そこで、requireのファイル依存関係をなくすためにbrowserifyを使います

$ sudo npm install -g browserify
$ browserify req.js -o req.bundle.js

browserify された req.bundle.js を espruino に流すと実行できるようになりました

$ ./espruino req.bundle.js
exp constructor
hello

browserifyで自動生成された req.bundle.js のファイルの中身

req.bundle.js
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
class exp {
  constructor() {
    console.log('exp constructor');
  }

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

module.exports = exp;


},{}],2:[function(require,module,exports){
const exp = require('./exp');
const e = new exp();
e.hello();


},{"./exp":1}]},{},[2]);

組込みCPUターゲット時のバイナリサイズ

最後に、組込みCPUをターゲットにしたときのバイナリサイズはどれくらいになるのかが気になったので確認しました。

評価ボード NUCLEO-F401RE をターゲットにしたときのバイナリサイズを確認。
組込みCPU STM32F401REFlash 512KB に対して Espruino 356KB とかなり容量取られてます。
ドライバ全部入りになってるはずなので削ることができればもう少し減らせるかも。

$ sudo apt install -y binutils-arm-none-eabi gcc-arm-none-eabi
$ make clean
$ BOARD=NUCLEOF401RE make
$ ls -lh
-rwxr-xr-x 1 pi pi 356K May 30 15:45 espruino_2v05.118_nucleof401re.bin
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js 学習ロードマップ 無料で学べる教材リンク

これからVue.jsを学ぶ方のためにVue.js学習ロードマップを作成しました。初学者向けです。

  • Vueを学ぶための各種リンクをまとめています。
  • 上から順番に記事や、ハンズオンをやると理解しやすいように順番も考慮して構成しています。なるべく初学者でも理解しやすいようにわかりやすい記事をチョイスしているつもりです。
  • 深い内容の理解が目的ではなくVueの全体感を掴むのが目的です。

1. Vue.jsとは何か大枠を理解している

以下の記事を読む


2. MVVMとは何か理解している

Vue.jsは基本的にはMVVMパターンです。
Model、View、ViewModelがあり、ViewにModelを渡して双方向バインディングしている作りです。
データを書き換えるとUIが変わり、UIを書き換えるとデータが変わる。
そこをバインディングさせることが効率的で画期的だったという経緯があります。
ただ、それをやりすぎてしまった場合、例えばシングルページアプリケーションなどで1枚のページ内でいろいろなコンポーネントが複雑に動くようなUIを作った場合、モデルを引き渡してバインディング、バインディング、バインディング・・・とやっていくと、思わぬところで副作用が起きてしまうことがあります。
これは、Vue.jsというよりはMVVMのつらいところではありますね。

この課題を解決しようとしたのが、ReactのReduxやFluxです。
歴史を振り返ると、jQueryの時代が終わり、その後MVVMの時代が到来しました。
その後、FacebookがReactを発表しましたが、それと同時にFluxアーキテクチャという考えが登場しました。
これはMVVMのアーキテクチャを否定するもので、双方向バインディングにおいては、コンポーネントとデータを分離させてしまい、データをいじるなら必ずActionを通る設計がいいと提唱したんです。
これによりソースコードの記述量は増えますが、データの流れは一方向になる。複雑性が生まれにくいというメリットがありますね。
このような考え方は今でも主流になっています。Vue.jsにおいてはVuexが取り入れています。

VuexはまさにFlux、Reduxに影響されて設計されたもので、Vue.jsで前述の問題を解決するものです。

『Vue.js入門』の執筆者が語る、はじめてのVue.js。本腰入れる前に知りたい5つのポイント〜私のVue.jsへの想いをのせて〜 | flexy(フレキシー)より上記文章は引用

以下の記事を読む


3. Vue.jsの基礎を理解している

以下の記事を読む


4. コンポーネントとは何か理解している

以下の記事を読む


5. Vueのライフサイクルフックについて理解している

以下の記事を読む

ここからハンズオンが始まります

6. 開発ツールを導入できている

以下の記事を読み導入する


7. 以下のビデオ学習を実施し、実際にコードを書く。

以下を実施する

  • Vue.js入門 - YouTube
    • これはCDN経由でVue本体を読み込み利用するパターンの解説動画です。ちょこっと試しでVueを試してみたいというやり方のため実務ではこの方法は使いませんがVueを理解するには有用なので実施しましょう。

8. Vueのv-model、算出プロパティ(computed)、メソッド(methods)、監視プロパティ(watch)、クラスのバインドについて理解している

以下を実施する

以下の記事を読む


9. VueのPropsについて理解している

以下の記事を読む


10. Vue CLIの基本について理解している

以下の記事を読む


11. 以下のビデオ学習を実施し、実際にコードを書き、Vue.js、Vue Routerを利用した実装ができる(余裕があればVuexも理解できるとGood)

以下の15-19は少し内容が古くCLIが現在ではバージョン4にバージョンが上がっています。
以下の用にファイルの出力箇所が変更になっているので読み替えて実施してください。
    The default directory structure was changed:
    src/store.js moved to src/store/index.js;
    src/router.js renamed to src/router/index.js;
    また動画はWindows環境ですが適宜Macであればに読み替えてください。

以下を実施する

以下を読む


12. おさらいとして以下の記事に目を通している

以下の記事を読む


13. Vue.jsを利用するメリットについて理解している

以下の記事を読む

おつかれさまでした!

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

【初心者】Puppeteerでよく使うコードベスト3

はじめに

毎月末になると、ルーティンで作業工数の入力をしています。
JavaScriptで半自動で入力していて、5分ほどで入力出来ています。
が、憂鬱すぎるので全自動化することにしました。
ググって最初に目に付いたPuppeteerを利用します。
実際に利用してみてベースとなるコード、更によく使うコードがわかったのでQiitaに残します。

コード

基本のコード

kihon.js
const puppeteer = require('puppeteer');
// ID・アカウント認証する時はここでID・アカウント情報を読み込む↓
// 後述
// ID・アカウント認証する時はここでID・アカウント情報を読み込む↑

(async () => {
    const browser = await puppeteer.launch({
        headless: false,  // ブラウザの動きを表示
        slowMo: 50  // puppeteerの操作を遅らせる
    })
    const page = await browser.newPage()

    // ページを開く
    await page.goto('https://www.google.com/')

    // 必要な処理を書く↓
    // 後述
    // 必要な処理を書く↑

    // ブラウザを閉じる
    await browser.close()
})()

ID・アカウント認証する時はここでID・アカウント情報を読み込む

const {USER, PWD} = require('./config.json'); // 認証が必要であれば別ファイルのconfigファイルから読み込む
config.jsonの中身は以下
config.json
{
    "USER":"xxx",
    "PWD":"xxx"
}

必要な処理(よく使うコード)

今回だと使うコードは決まっていて、必要な値を入力して、ボタンをクリックして登録するだけでしたので以下の操作で事足りました。

// 指定した時間待つ
await page.waitFor(10000); // ミリ秒

// 入力
await page.type("#IdUser", 'userName'); // セレクタ,入力文字。
await page.type("#IdUser", USER); // 変数の場合

// クリック
await page.click("#loginButton"); // セレクタ

// テキスト取得
const text = await page.$eval('td.timeHour', text => text.textContent) // セレクタ

最終的なコード

kihonプラスα.js
const puppeteer = require('puppeteer');
// ID・アカウント認証する時はここでID・アカウント情報を読み込む
const {USER, PWD} = require('./config.json'); // 認証が必要であれば別ファイルのconfigファイルから読み込む

(async () => {
    const browser = await puppeteer.launch({
        headless: false,  // ブラウザの動きを表示
        slowMo: 50  // puppeteerの操作を遅らせる
    })
    const page = await browser.newPage()

    // ページを開く
    await page.goto('https://www.google.com/')

    // 必要な処理を書く
    // 指定した時間待つ
    await page.waitFor(10000); // ミリ秒
    // 入力
    await page.type("#IdUser", USER); // 変数の場合
    // クリック
    await page.click("#loginButton"); // セレクタ
    // テキスト取得
    const text = await page.$eval('td.timeHour', text => text.textContent) // セレクタ

    // ブラウザを閉じる
    await browser.close()
})()

参考

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

閲覧しているページをツイートするブックマークレット

Bookmarklet を 2 つ紹介(動画再生速度の変更、ツイート) - Qiita https://qiita.com/irisTa56/items/c47cb1bc1ea74126dc0e#bookmarklet-%E3%81%A7%E3%82%A6%E3%82%A7%E3%83%96%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%92%E5%8F%82%E7%85%A7%E3%81%97%E3%81%9F-tweet-%E3%81%99%E3%82%8B

の閲覧しているページをツイートするブックマークレットが
2020/05/30時点ではwindows10のchromeから稼働しないので試してみたら

/intent/tweet?text=

のパスがtwitter側で無効になったっぽい。
ios用のブックマークレットとして紹介されていた

/compose/tweet?text={text}&url={url}

だとpcでも稼働することを確認したので以下になります。

javascript:window.open("https://twitter.com/compose/tweet?text="+encodeURIComponent(document.title)+"&url= "+encodeURIComponent(location.href))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Javascript] Textareaの高さを自動調整しようとしてだいぶハマった件についてのメモ

やりたかったこと

動的生成したTextarea群(Twitterのようなもの)について、textareaの内容のscrollHeightによって、生成時点で高さを自動調整したかった。

作りたい関数の流れ

  • 対象のテキストエリアを取得する
  • すべての要素に対してループ
    • テキストエリア1つ1つに対して最低行数を設定したい
    • scrollHeightを取得して、テキストエリアから溢れていれば大きくし、逆に余っていれば小さくしてscrollHeightに合わせたい

問題点と解決法

動的生成の件

  1. 生成した時点で一旦調整したかったが、$(document).on()ではそもそもイベントが発生しないので無理。
    • 生成後に関数を実行することに。

scrollHeight

  1. display:none;だとscrollHeightを取得できない。
    • 取得してからdisplay:none;する。
  2. javascriptネイティブなら、textareaオブジェクト.scrollHeightでOK
  3. $(セレクタ).scrollHeightはなぜか動かない。
    • $(セレクタ).get(0).scrollHeightとする。(オブジェクトが単体でもこうしないと動かない)
  4. テキストエリア生成時、内容が1行分に収まる場合でも、なぜか2行になってしまう。
    • テキストエリアにはデフォルトでrows=2, cols=20が設定されているようだ。
    • そもそもscrollHeightというのはlineHeightにrowsを掛けたものである。
    • すると、scrollHeightを取得しても、lineHeight*1とならず、lineHeight*2となってしまう。
    • 1行まで小さくしたいテキストエリアには、rows=1を設定しておく必要がある。
  5. 5行の内容を削って3行にした場合などに、小さくなってくれない。
    • scrollHeightは内容の高さを見るのではなく、表示領域全体の高さを参照しているようだ。
    • おそらく、5行分に大きくした時にrowsが5に更新されているためである。内容を削ってもrowsは小さくならないのでscrollHeightも減らない。
    • 一旦テキストエリアを小さくしてわざと溢れさせてから(rowsを変更してもいいかも?)、scrollHeightを取得する。

lineHeight

  1. javascriptネイティブだと、Textareaオブジェクト、もしくはElementオブジェクトにはlineHeightがない。
  2. CSSで指定している場合、オブジェクト.style.lineHeightでは取得できない。
  3. DOMにstyle="line-height:**px"が直接書かれていれば取れる。

最終的なコード

jQueryの場合

const adjust_textarea = (tab) => {
    //jquery
    const $target = $("#"+tab+"_data_area textarea");
    $target.each((i,elem) => {
        //最低高さ設定
        let minheight;
        const lineheight = parseInt($(elem).css("line-height").split("px")[0]);
        if($(elem).hasClass("ta_rows1")) minheight = lineheight;
            else if($(elem).hasClass("ta_rows2")) minheight = lineheight * 2;
            else minheight = lineheight * 3;
        //高さ変更
        $(elem).height(minheight); //scrollHeightはheightに満たない場合はheightが返るので一旦最小にしてあふれさせる
        const scrollheight = $(elem).get(0).scrollHeight; //lineheight * 行数が入る
        if(scrollheight > minheight) $(elem).height(scrollheight);
    });
};

Javascriptネイティブの場合(却下)

    const taList = document.querySelectorAll("#"+tab+"_data_area textarea");
    const length = taList.length;
    if(0) for(let i=0; i<length; i++) {
        const scrollheight = taList[i].scrollHeight; //javascriptのscrollHeightは、rowsにline-heightを掛けたもの
        if(scrollheight > minheight) {
            if(scrollheight > taList[i].clientHeight) {
                taList[i].style.height = (scrollheight+2) + "px"; //style.heightはborder含む
            } else {
                taList[i].style.height = (minheight+2) + "px";
                if(scrollheight > taList[i].clientHeight) {
                    taList[i].style.height = (scrollheight+2) + "px";
                }
            }
        } else {
            taList[i].style.height = (minheight+2) + "px";
        }
    }

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

JavaScriptの関数と宣言

概要

JavaScript Primerを読んでてワチャワチャしてきた部分を整理する。
基本的には書いてあることそのままです。
参考:https://jsprimer.net/basic/function-declaration/

関数宣言

functionから始まる文のこと。

const huga = true;
function hoge(huga)
{
  return huga === true;
}

console.log(hoge(huga));  // => true

返り値を省略した場合、return文を省略した場合はundefinedを返す。

function hoge()
{
  return;
}

function huga()
{
}

console.log(hoge());  // => undefined
console.log(huga());  // => undefined

functionキーワードを使用した関数式

関数式とは、関数を値として変数へ代入している式のことを言います。

関数が値として扱えることをファーストクラスファンクション(第一級関数)と呼ぶ。
関数式の定義方法は以下の通り。

const hoge = function()
{
  return 'hoge';
};

上記のように関数名を省略して書くことができる。
このような名前を持たない関数を匿名関数(または無名関数)と呼ぶ。

Arrow Functionを使用した関数式

const hoge = () =>
{
  return 'hoge';
}

カッコイイですね。
省略記法がいっぱいあるので使いこなしたいところです。
省略記法のルールは以下の通り。

  • 関数の仮引数が1つのときは()を省略できる。
  • 関数の処理が1つの式である場合に、ブロックとreturn 文を省略できる。
    • その式の評価結果を return の返り値とする。
const hoge = () => {return 'hoge';}; // 仮引数がないとき
const hoge = (huga) => {return 'huga';}; // 仮引数が1つのみのとき
const hoge = huga => {return 'huga';}; // 仮引数が1つのみのときは()を省略可能
const hoge = (huga, piyo) => {return huge + piyo;}; // 仮引数が複数のとき

以下の2つは同じ意味となる。

const hoge = (huga) => {return huga * huga;};
const hoge = huga => huga * huga; // 1行のみの場合はreturnとブロックを省略できる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

工数管理ツールを開発してみた

やったこと

工数を管理するウェブアプリを開発しました。
言語:Java(Spring), JavaScript

手順
1. プロジェクトを作成(この際に、プロジェクトコードを登録する)
2. 1.で登録したプロジェクトコードを入力することで、追加/編集/閲覧したいプロジェクトに遷移
3. タスク追加(タスク名、工数、開始日、終了日、担当者名など)
4. タスク閲覧(担当者がある期間にどんなタスクをどれだけの工数で終えたのか、プロジェクトの担当者がそれぞれどれだけの工数を費やしているか、など)

画面

プロジェクトコード入力画面
スクリーンショット 2020-05-30 13.53.52.png

タスク追加(および修正)画面
スクリーンショット 2020-05-30 13.54.28.png

タスク閲覧画面
スクリーンショット 2020-05-30 13.53.05.png

詰まったこと

JavaScriptとThymeleafの連携?

前回の記事にも書きましたが、例えば、JavaScriptで

target.html('<a th:href="@{/hello}">サンプル</a>');

と書いても、aタグが機能しなかったりしました。

解決策

<script type="text/javascript" th:inline="javascript">
    const link = /*[[@{/hello}]]*/'';
    target.html('<a href="' + link + '">サンプル</a>');
</script>

と書いてあげることで解決しました。
このように、JavaScriptでThymeleafのth:が使えない、といったことに苦戦しました。

tableタグ内にformタグを書けない問題

例えば、

<table>
    <tr>
        <th>#</th>
        <th>名前</th>
        <th>ボタン</th>
    </tr>
    <tr>
        <form>
            <td>1</td>
            <td><input type="text"/></td>
            <td><input type="submit"></td>
        </form>
    </tr>
    <tr>
        <form>
            <td>2</td>
            <td><input type="text" /></td>
            <td><input type="submit"></td>
        </form>
    </tr>
</table>

画像だと、
スクリーンショット 2020-05-30 14.21.26.png

こんなケースです。これをChromeのディベロッパーツールで見てみると、、、
スクリーンショット 2020-05-30 14.27.43.png

おかしい。。。formタグ、そこで閉じちゃダメ。。。

解決策

  1. formタグにid属性を付与する
  2. inputタグのform属性に1.で付与したidを記載する

こうすることで、formタグのidに対応したinputのvalue値を送信することができます。
ちなみに、formタグはinputタグの上でも下でもどこでも記載して良いそうです。

上の例だと

<table>
    <tr>
        <th>#</th>
        <th>名前</th>
        <th>ボタン</th>
    </tr>
    <tr>
        <form id="form_01"></form>
        <td>1</td>
        <td><input type="text" form="form_01" /></td>
        <td><input type="submit" form="form_01"></td>
    </tr>
    <tr>
        <form id="form_02"></form>
        <td>2</td>
        <td><input type="text" form="form_02" /></td>
        <td><input type="submit" form="form_02"></td>
    </tr>
</table>

ディベロッパーツールで見ると

スクリーンショット 2020-05-30 14.32.57.png

このようになっているはずです!おそらくこれでうまくいきます。

以上です。最後まで読んでくださりありがとうございました。

参考

formタグは入れ子にできない&その対処法
HTML5 FORMとINPUTを分けて記述する方法

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

【JavaScript】おみくじアプリを作ってみた

学習ポイント

■ランダムメソッドの使い方

■配列から値を取り出す

■特定の値にアラートを出す

<body>

<h1>おみくじ</h1>
<button id = "start">おみくじスタート!</button>
<div id = "result">
  大吉
</div>

</body>
<script>
  //DOM操作でJSでHTMLの操作を可能にする
  const start = document.getElementById("start");
  const result = document.getElementById("result");

  const omikuji = ["大吉","中吉","吉","凶","大凶",]

  num = 0

  //スタート押したら開始されるメソッド
  start.addEventListener("click",function() {

      //ランダムに選ぶ
    const num = Math.floor(Math.random() * omikuji.length);

    //配列を表示する
    result.textContent = omikuji[num];

    //特定の値にアラートを出す
      if(result.textContent === "大吉"){
        alert("おめでとう!")
      }else if(result.textContent === "大凶"){
        alert("残念でした!")
      }
  })
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【VSCode拡張機能】Turbo Console Logでconsole.logをショートカットで効率化しよう!

console.logのコピペがめんどくさい

JavaScript/TypeScriptで開発していると、console.logの使用は避けて通れません。
適宜console.logをコピペして中の変数書き換えて、どこのconsole.logか分かるように文字列で名前を記述したり...とても面倒くさいとモヤモヤしておりました。
スニペットでconsole.log()は補完してくれますが、中身書くのがめんどくさい。。。

そんな時にショートカットコマンド一発でconsole.logできるVSCodeの拡張機能を見つけました。

Turbo Consolo Log

「Turbo Console Log」という拡張機能です。
VSCodeの拡張機能Marketplaceから検索。
これです。設定とかは不要でインストールするだけ。
スクリーンショット 2020-05-30 12.27.23.png

使い方

使い方は簡単で

  • console.log対象の変数を選択
  • ショートカットコマンドを入力
  • (Mac) shift + option + L  (フォーマッターを入れている場合上記コマンドと被るのでcontrol + option + Lの場合あり)
  • (Windows) ctrl + alt + L

どこの変数なのかプレフィックスまでつけてくれます。
ezgif.com-video-to-gif.gif

その他のショートカット

※Macでフォーマッターを入れている場合control + option + ~の場合あり

  • (Mac): Option + Shift + C / (Win): ctrl + alt + C

拡張機能で出力した全部のconsole.logをコメントアウト

  • (Mac): Option + Shift + U / (Win): ctrl + alt + U

拡張機能で出力した全部の出力したすべてのconsole.logのコメントアウトを削除

  • (Mac): Option + Shift + D / (Win): ctrl + alt + D

拡張機能で出力した全部の出力したすべてのconsole.logを削除
ezgif.com-video-to-gif (1).gif

細かな設定

拡張機能の設定画面から
console.log末尾のセミコロンの有無や、シングルクオート/ダブルクオートの選択
プレフィックスのルール設定ができるので自分の好みに合わせて設定できます。
スクリーンショット 2020-05-30 13.25.32.png

スクリーンショット 2020-05-30 13.26.11.png

地味だけど使い始めると快適な拡張機能のご紹介でした。
最後まで読んで頂きありがとうございました。

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

ESLintでautofixに対応していないルールをautofixする

やりたいこと

ESLintでは、autofixに対応しているルールと、していないルールがあります。
たとえば
no-varはautofixされますが、
no-unused-varsはautofixされません。

勝手に修正されると困るものはautofixされない仕様なのでしょうが、
それでも「no-unused-varsを一括で修正したいなー」みたいなケースはあります。

eslint-plugin-autofixで解決

https://www.npmjs.com/package/eslint-plugin-autofix
これを使うと、標準ではautofixできないルールをautofixしてくれちゃいます!

pluginsの設定を入れて、
"autofix/ルール名"で列挙すればOK!

.eslintrc
{
  "plugins": ["autofix"],
  "rules": {
    "autofix/no-unused-vars": "error",
    "autofix/no-plusplus": "error"
  }
}
before
var hoge = 1
var fuga = 2
hoge++
console.log(hoge)

after
var hoge = 1

hoge+=1
console.log(hoge)

いい感じ!

補足

全部のルールに対応しているわけではないっぽくて、
試したらeqeqeqなんかはダメでしたねー。
ドキュメント読んでも、未対応ルールは良く分らなかった。
まずは試してみるのが良いと思います。

常に有効にしてしまうとコードが壊れることもありそうなので、
「ここぞ」というタイミングで使うのが良いのかなーと思います。

ではまた~。

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

RailsでJavascriptを使う

Rails5.1以降では、webpackがJSの管理パッケージとして導入されており、以前で使われていたsprocketsはデフォルトでは装備されなくなっています。
ApplicationHtmlをみてみると、application_pack_includeなんちゃらみたいなコードがありますが、これがファイルにあるjsのコードを全て読み込んでapplication.jsに送っています。
sprocketsの場合だと、application_something_includeなんちゃらだったと思います。

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

Javascriptソースコードを隠蔽してみた。

はじめに

javascriptはいやおうなしに見られてしまうので、
APIキーとかソースとか隠せないかな、と考えて色々調べてみた結果、ここに行き着いた。

現状のままではPHPにJsソースを書くことになる欠点がある……ので、これの対策verもそのうち記事にします。

どうやって実装するの?

まずもって、ユーザーがJavascriptのソースコードをのぞき見する主な手段は以下の2パターンある。

  • URLバーに該当するJsのURLを直接打ち込んで、ページとして見る
  • 右クリック→検証→左メニューの「Sources」から閲覧する

逆に言えば、今回やるべきはこの2つの手段を両方とも潰すことである。
それを踏まえて、本システムの要件は以下のとおりとする。

要件

  • ① 直接アクセスした場合にはエラーorニセのコードを出力し、ソースコードを見られないようにすること
  • ② 右クリック→検証→左メニューの「Sources」を使ってもソースコードが見られないようにすること
  • ③ あらかじめ決めておいたサイト以外からは呼び出せないように設定する(場合によっては必要になる。理由は後述)

Step1 直接アクセスの阻止

まず直接URLを指定してページを覗かれた場合だが、これは単純なJavascriptだけでは阻止しきれない。
なぜならJavascriptがクライアントサイドで動く言語だからだ。

pic1-1.png
(※拾い画)

この図はウェブサイトが開かれ、見られるようになるまでの一連の流れ。

javascriptのようなクライアントサイド言語はまずコード全体が閲覧者の端末に送信され、閲覧者の端末の中(図の⑤)で処理が実行される。見せたい見せたくないに関わらずページを開いた瞬間にコードを渡してしまうので、そのあとから隠蔽するのって難しいのだ。

ならばどうするかというと、「サーバーサイド言語」というやつを経由させる。
こいつは図で言うと③で実行される言語で、サーバーマシンの中で処理を行ってからその結果だけを返すようになっている。
ということは、このサーバーサイド言語で直接アクセスかどうかを判定してやればよいわけだ。

今回はPHPを利用してこの処理を実装する。
サーバー内で処理できればそれでいいので、処理自体はPythonでもRubyでもNode.jsでも実装できる……はず。

とりあえず、以下のようにソースを書いてみた。

source.php
<?php

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */
    echo "本命のソース!";

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'アクセス……拒否しますっ!!(>_<)';
}

?>

直接アクセスかどうかのチェックには、リクエストメソッドの状態を取得する$_SERVER["REQUEST_METHOD"]を用いよう。
この値は通常GETになっていて、後述するような特殊なアクセスを行った場合のみPOSTになる。
直接アクセスの場合はもちろんGETになっているので見れないというわけだ。

Step1 隠したいJavascriptコードの用意

隠したいJsソースを用意する。

javascript
function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    document.body.appendChild(block);
    /* ここまで */
};

このコードは後述のXMLHttpRequestで読み込み、そのあとに削除する。
そのため、ここに記載するコードはオブジェクト化しておく必要がある。
平たく言うと一つのfunction内にまとめておかなくてはならない。

で、これをStep1のPHPと組み合わせた結果が以下のとおり。

source.php
<?php

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */
    echo "function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    document.body.appendChild(block);
    /* ここまで */
};";

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'アクセス……拒否しますっ!!(>_<)';
}

?>

これで、要件①はクリア。
直接URLからページを見てもコードは読めなくなった。

Step3 XMLHttpRequestでの呼び出し

次に呼び出し処理を作成する。
呼び出しには、javascriptのXMLHttpRequestを用いる。

そのままコピペできる内容なので、とりあえずはコードを記載する。

javascript
// 実行
ReadSource(document.body, './source.php');

function ReadSource(target, urls){

    // 宣言
    let request = new XMLHttpRequest();

    // リクエストURLを設定(リクエストタイプはPOSTにすること)
    request.open('POST', urls);

    // リクエスト先からレスポンス(返答)があった場合の処理
    request.onreadystatechange = function(){
        if ( (request.readyState == 4) && (request.status == 200) ){

            let scrpt = document.createElement("script");
            scrpt.type = 'text/javascript';
            scrpt.innerHTML = request.responseText;
            target.appendChild(scrpt);

            // 消去
            target.removeChild(scrpt);

            // 処理 
            HideSource();
        }
    }
    request.send();
}

<script>リンクでファイルを読むのではなく、
外部から読み込んだテキストをhtml上に貼り付け、それをJsとして解釈させているから検証メニューにも載ることはない。
代わりにHTMLソース上に関数HideSource全文が載ってしまうので、それは削除する必要がある。

挙動を見る限り、ソースコードをappendChildした時点で関数全体がどこかに登録される模様。
なので、一度appendChildでhtml上に追加してしまえば消しても大丈夫。

このコードではHideSourceの実行が行われるより先にソースコードを消してしまっているが、
その場合でもReadSource内でHideSourceを実行しておけば問題なく動作する。

で、上記のコードを実際にページに記述してみよう。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>たいとる</title>
</head>
<body>

<div id="point"></div>

<script type="text/javascript">
function ReadSource(target, urls) {
    // 宣言
    let request = new XMLHttpRequest();

    // リクエストURLを設定(リクエストタイプはPOSTにすること)
    request.open('POST', urls);

    // リクエスト先からレスポンス(返答)があった場合の処理
    request.onreadystatechange = function(){
        if ( (request.readyState == 4) && (request.status == 200) ){

            let scrpt = document.createElement("script");
            scrpt.type = 'text/javascript';
            scrpt.innerHTML = request.responseText;
            target.appendChild(scrpt);

            // 消去
            target.removeChild(scrpt);

            // 処理 
            HideSource();
        }
    }
    request.send();
}
// 実行
ReadSource(document.getElementById('point'), './source.php');
</script>
</body>
</html>

要件②もクリア。
これで、ソースであるsource.phpを見せないようにしつつ、秘匿したJsを実行できる。やったね。

※完成形のコードはGithubに置いています。
「Clone or download」→「Download ZIP」でダウンロードできます。
PHPを含むので、PHPが動く実行環境が必要というのは言わずもがな。

おまけ XMLHttpRequestとCORS

残るは要件③「あらかじめ決めておいたサイト以外からは呼び出せないように設定する」だが、
これ実は自動的にクリアしている。

今回使ったXMLHttpRequestだが、こいつにはCORSというルールで制限がかけられている。
詳しい説明はこちらの記事に譲るが……すごーーく雑な説明をすると、

XMLHttpRequestで別のファイルを呼び出すときに呼び出し元と呼び出し先のサイトが異なった場合、呼び出される側に『このサイトはオレを呼び出していいぞ!』という許可証をつけておけよ?でなけりゃ呼び出しを拒否するぞ1

ということ。
今回は呼び出し元(index.html)と呼び出し先(source.php)を同じ階層に同梱しているのでこの制限にはあたらない。

が、もし仮に別サイトから呼び出すことを考慮したい場合、source.phpの文頭にheader()を追記して以下のように変更しよう。

source.php
<?php

header('Access-Control-Allow-Origin: https://example.com'); // この部分を追記。 https://example.comのところは呼び出し元のドメインを指定。

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */
    echo "function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    document.body.appendChild(block);
    /* ここまで */
};";

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'アクセス……拒否しますっ!!(>_<)';
}

?>

参考文献

http://pc110.club/php/%EF%BD%8Aavascript-hidden
http://labs.vividworks.jp/javascript-%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AF%E9%9A%A0%E8%94%BD%E3%81%99%E3%82%8B%E3%81%B9%E3%81%8D%E3%81%8B%EF%BC%9F-vol-4/
https://tenderfeel.xsrv.jp/mootools/705/


  1. 正確には「オリジン」というやつをを比較する。わからなければ、ドメインと同じものだと思っておけばいい。 

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

Javascriptを隠蔽化してみた。

はじめに

javascriptはいやおうなしに見られてしまうので、
APIキーとかソースとか隠せないかな、と考えて色々調べてみた結果、ここに行き着いた。

現状のままではPHPにJsソースを書くことになる欠点がある……ので、これの対策verもそのうち記事にします。

どうやって実装するの?

まずもって、ユーザーがJavascriptのソースコードをのぞき見する主な手段は以下の2パターンある。

  • URLバーに該当するJsのURLを直接打ち込んで、ページとして見る
  • 右クリック→検証→左メニューの「Sources」から閲覧する

逆に言えば、今回やるべきはこの2つの手段を両方とも潰すことである。
それを踏まえて、本システムの要件は以下のとおりとする。

要件

  • ① 直接アクセスした場合にはエラーorニセのコードを出力し、ソースコードを見られないようにすること
  • ② 右クリック→検証→左メニューの「Sources」を使ってもソースコードが見られないようにすること
  • ③ あらかじめ決めておいたサイト以外からは呼び出せないように設定する(場合によっては必要になる。理由は後述)

Step1 直接アクセスの阻止

まず直接URLを指定してページを覗かれた場合だが、これは単純なJavascriptだけでは阻止しきれない。
なぜならJavascriptがクライアントサイドで動く言語だからだ。

pic1-1.png
(※拾い画)

この図はウェブサイトが開かれ、見られるようになるまでの一連の流れ。

javascriptのようなクライアントサイド言語はまずコード全体が閲覧者の端末に送信され、閲覧者の端末の中(図の⑤)で処理が実行される。見せたい見せたくないに関わらずページを開いた瞬間にコードを渡してしまうので、そのあとから隠蔽するのって難しいのだ。

ならばどうするかというと、「サーバーサイド言語」というやつを経由させる。
こいつは図で言うと③で実行される言語で、サーバーマシンの中で処理を行ってからその結果だけを返すようになっている。
ということは、このサーバーサイド言語で直接アクセスかどうかを判定してやればよいわけだ。

今回はPHPを利用してこの処理を実装する。
サーバー内で処理できればそれでいいので、処理自体はPythonでもRubyでもNode.jsでも実装できる……はず。

とりあえず、以下のようにソースを書いてみた。

source.php
<?php

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */
    echo "本命のソース!";

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'アクセス……拒否しますっ!!(>_<)';
}

?>

直接アクセスかどうかのチェックには、リクエストメソッドの状態を取得する$_SERVER["REQUEST_METHOD"]を使う。
この値は通常GETになっていて、後述するような特殊なアクセスを行った場合のみPOSTになる。
直接アクセスの場合はもちろんGET。

Step1 隠したいJavascriptコードの用意

隠したいJsソースを用意する。

javascript
function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    document.body.appendChild(block);
    /* ここまで */
};

このコードは後述のXMLHttpRequestで読み込み、そのあとに削除する。
そのため、ここに記載するコードはオブジェクト化しておく必要がある。
平たく言うと一つのfunction内にまとめておかなくてはならない。

で、これをStep1のPHPと組み合わせた結果が以下のとおり。

source.php
<?php

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */
    echo "function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    document.body.appendChild(block);
    /* ここまで */
};";

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'アクセス……拒否しますっ!!(>_<)';
}

?>

これで、要件①はクリア。
直接URLからページを見てもコードは読めなくなった。

Step3 XMLHttpRequestでの呼び出し

次に呼び出し処理を作成する。
呼び出しには、javascriptのXMLHttpRequestを用いる。

そのままコピペできる内容なので、とりあえずはコードを記載する。

javascript
// 実行
ReadSource(document.body, './source.php');

function ReadSource(target, urls){

    // 宣言
    let request = new XMLHttpRequest();

    // リクエストURLを設定(リクエストタイプはPOSTにすること)
    request.open('POST', urls);

    // リクエスト先からレスポンス(返答)があった場合の処理
    request.onreadystatechange = function(){
        if ( (request.readyState == 4) && (request.status == 200) ){

            let scrpt = document.createElement("script");
            scrpt.type = 'text/javascript';
            scrpt.innerHTML = request.responseText;
            target.appendChild(scrpt);

            // 消去
            target.removeChild(scrpt);

            // 処理 
            HideSource();
        }
    }
    request.send();
}

<script>リンクでファイルを読むのではなく、
外部から読み込んだテキストをhtml上に貼り付け、それをJsとして解釈させているから検証メニューにも載ることはない。
代わりにHTMLソース上に関数HideSource全文が載ってしまうので、それは削除する必要がある。

挙動を見る限り、ソースコードをappendChildした時点で関数全体がどこかに登録される模様。
なので、一度appendChildでhtml上に追加してしまえば消しても大丈夫。

このコードではHideSourceの実行が行われるより先にソースコードを消してしまっているが、
その場合でもReadSource内でHideSourceを実行しておけば問題なく動作する。

で、上記のコードを実際にページに記述してみよう。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>たいとる</title>
</head>
<body>

<div id="point"></div>

<script type="text/javascript">
function ReadSource(target, urls) {
    // 宣言
    let request = new XMLHttpRequest();

    // リクエストURLを設定(リクエストタイプはPOSTにすること)
    request.open('POST', urls);

    // リクエスト先からレスポンス(返答)があった場合の処理
    request.onreadystatechange = function(){
        if ( (request.readyState == 4) && (request.status == 200) ){

            let scrpt = document.createElement("script");
            scrpt.type = 'text/javascript';
            scrpt.innerHTML = request.responseText;
            target.appendChild(scrpt);

            // 消去
            target.removeChild(scrpt);

            // 処理 
            HideSource();
        }
    }
    request.send();
}
// 実行
ReadSource(document.getElementById('point'), './source.php');
</script>
</body>
</html>

要件②もクリア。
これで、ソースであるsource.phpを見せないようにしつつ、秘匿したJsを実行できる。やったね。

※完成形のコードはGithubに置いています。
「Clone or download」→「Download ZIP」でダウンロードできます。
PHPを含むので、PHPが動く実行環境が必要というのは言わずもがな。

おまけ XMLHttpRequestとCORS

残るは要件③「あらかじめ決めておいたサイト以外からは呼び出せないように設定する」だが、
これ実は自動的にクリアしている。

今回使ったXMLHttpRequestだが、こいつにはCORSというルールで制限がかけられている。
詳しい説明はこちらの記事に譲るが……すごーーく雑な説明をすると、

XMLHttpRequestで別のファイルを呼び出すときに呼び出し元と呼び出し先のサイトが異なった場合、呼び出される側に『このサイトはオレを呼び出していいぞ!』という許可証をつけておけよ?でなけりゃ呼び出しを拒否するぞ1

ということ。
今回は呼び出し元(index.html)と呼び出し先(source.php)を同じ階層に同梱しているのでこの制限にはあたらない。

が、もし仮に別サイトから呼び出すことを考慮したい場合、source.phpの文頭にheader()を追記して以下のように変更しよう。

source.php
<?php

header('Access-Control-Allow-Origin: https://example.com'); // この部分を追記。 https://example.comのところは呼び出し元のドメインを指定。

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */
    echo "function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    document.body.appendChild(block);
    /* ここまで */
};";

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'アクセス……拒否しますっ!!(>_<)';
}

?>

参考文献

http://pc110.club/php/%EF%BD%8Aavascript-hidden
http://labs.vividworks.jp/javascript-%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AF%E9%9A%A0%E8%94%BD%E3%81%99%E3%82%8B%E3%81%B9%E3%81%8D%E3%81%8B%EF%BC%9F-vol-4/
https://tenderfeel.xsrv.jp/mootools/705/


  1. 正確には「オリジン」というやつをを比較する。わからなければ、ドメインと同じものだと思っておけばいい。 

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

Javascriptをユーザーから見られないようにしてみた。

はじめに

javascriptはいやおうなしに見られてしまうので、
APIキーとかソースとか隠せないかな、と考えて色々調べてみた結果、ここに行き着いた。

現状のままではPHPにJsソースを書くことになる欠点がある……ので、これの対策verもそのうち記事にします。

※完成形のコードはGithubに置いています。
「Clone or download」→「Download ZIP」でダウンロードできます。
PHPを含むので、PHPが動く実行環境が必要というのは言わずもがな。

どうやって実装するの?

まずもって、ユーザーがJavascriptのソースコードをのぞき見する主な手段は以下の2パターンある。

  • URLバーに該当するJsのURLを直接打ち込んで、ページとして見る
  • 右クリック→検証→左メニューの「Sources」から閲覧する

逆に言えば、今回やるべきはこの2つの手段を両方とも潰すことである。
それを踏まえて、本システムの要件は以下のとおりとする。

要件

  • ① 直接アクセスした場合にはエラーorニセのコードを出力し、ソースコードを見られないようにすること
  • ② 右クリック→検証→左メニューの「Sources」を使ってもソースコードが見られないようにすること
  • ③ あらかじめ決めておいたサイト以外からは呼び出せないように設定する(場合によっては必要になる。理由は後述)

Step1 直接アクセスの阻止

まず直接URLを指定してページを覗かれた場合だが、これは単純なJavascriptだけでは阻止しきれない。
なぜならJavascriptがクライアントサイドで動く言語だからだ。

pic1-1.png
(※拾い画)

この図はウェブサイトが開かれ、見られるようになるまでの一連の流れ。

javascriptのようなクライアントサイド言語はまずコード全体が閲覧者の端末に送信され、閲覧者の端末の中(図の⑤)で処理が実行される。見せたい見せたくないに関わらずページを開いた瞬間にコードを渡してしまうので、そのあとから隠蔽するのって難しいのだ。

ならばどうするかというと、「サーバーサイド言語」というやつを経由させる。
こいつは図で言うと③で実行される言語で、サーバーマシンの中で処理を行ってからその結果だけを返すようになっている。
ということは、このサーバーサイド言語で直接アクセスかどうかを判定してやればよいわけだ。

今回はPHPを利用してこの処理を実装する。
サーバー内で処理できればそれでいいので、処理自体はPythonでもRubyでもNode.jsでも実装できる……はず。

とりあえず、以下のようにソースを書いてみた。

source.php
<?php

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */
    echo "本命のソース!";

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'アクセス……拒否しますっ!!(>_<)';
}

?>

直接アクセスかどうかのチェックには、リクエストメソッドの状態を取得する$_SERVER["REQUEST_METHOD"]を使う。
この値は通常GETになっていて、後述するような特殊なアクセスを行った場合のみPOSTになる。
直接アクセスの場合はもちろんGET。

Step1 隠したいJavascriptコードの用意

隠したいJsソースを用意する。

javascript
function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    document.body.appendChild(block);
    /* ここまで */
};

このコードは後述のXMLHttpRequestで読み込み、そのあとに削除する。
そのため、ここに記載するコードはオブジェクト化しておく必要がある。
平たく言うと一つのfunction内にまとめておかなくてはならない。

で、これをStep1のPHPと組み合わせた結果が以下のとおり。

source.php
<?php

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */
    echo "function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    document.body.appendChild(block);
    /* ここまで */
};";

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'アクセス……拒否しますっ!!(>_<)';
}

?>

これで、要件①はクリア。
直接URLからページを見てもコードは読めなくなった。

Step3 XMLHttpRequestでの呼び出し

次に呼び出し処理を作成する。
呼び出しには、javascriptのXMLHttpRequestを用いる。

そのままコピペできる内容なので、とりあえずはコードを記載する。

javascript
// 実行
ReadSource(document.body, './source.php');

function ReadSource(target, urls){

    // 宣言
    let request = new XMLHttpRequest();

    // リクエストURLを設定(リクエストタイプはPOSTにすること)
    request.open('POST', urls);

    // リクエスト先からレスポンス(返答)があった場合の処理
    request.onreadystatechange = function(){
        if ( (request.readyState == 4) && (request.status == 200) ){

            let scrpt = document.createElement("script");
            scrpt.type = 'text/javascript';
            scrpt.innerHTML = request.responseText;
            target.appendChild(scrpt);

            // 消去
            target.removeChild(scrpt);

            // 処理 
            HideSource();
        }
    }
    request.send();
}

<script>リンクでファイルを読むのではなく、
外部から読み込んだテキストをhtml上に貼り付け、それをJsとして解釈させているから検証メニューにも載ることはない。
代わりにHTMLソース上に関数HideSource全文が載ってしまうので、それは削除する必要がある。

挙動を見る限り、ソースコードをappendChildした時点で関数全体がどこかに登録される模様。
なので、一度appendChildでhtml上に追加してしまえば消しても大丈夫。

このコードではHideSourceの実行が行われるより先にソースコードを消してしまっているが、
その場合でもReadSource内でHideSourceを実行しておけば問題なく動作する。

で、上記のコードを実際にページに記述してみよう。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>たいとる</title>
</head>
<body>

<div id="point"></div>

<script type="text/javascript">
function ReadSource(target, urls) {
    // 宣言
    let request = new XMLHttpRequest();

    // リクエストURLを設定(リクエストタイプはPOSTにすること)
    request.open('POST', urls);

    // リクエスト先からレスポンス(返答)があった場合の処理
    request.onreadystatechange = function(){
        if ( (request.readyState == 4) && (request.status == 200) ){

            let scrpt = document.createElement("script");
            scrpt.type = 'text/javascript';
            scrpt.innerHTML = request.responseText;
            target.appendChild(scrpt);

            // 消去
            target.removeChild(scrpt);

            // 処理 
            HideSource();
        }
    }
    request.send();
}
// 実行
ReadSource(document.getElementById('point'), './source.php');
</script>
</body>
</html>

要件②もクリア。
これで、ソースであるsource.phpを見せないようにしつつ、秘匿したJsを実行できる。やったね。

※完成形のコードはGithubに置いています。
「Clone or download」→「Download ZIP」でダウンロードできます。
PHPを含むので、PHPが動く実行環境が必要というのは言わずもがな。

おまけ XMLHttpRequestとCORS

残るは要件③「あらかじめ決めておいたサイト以外からは呼び出せないように設定する」だが、
これ実は自動的にクリアしている。

今回使ったXMLHttpRequestだが、こいつにはCORSというルールで制限がかけられている。
詳しい説明はこちらの記事に譲るが……すごーーく雑な説明をすると、

呼び出し元と呼び出し先のサイトが別々だったときは呼び出される側に『このサイトはオレを呼び出していいぞ!』という許可証をつけておいてくださいね。許可証なしで他人のサイトからファイル持ってこようとするなら接続させませんよ1

ということ。
今回は呼び出し元(index.html)と呼び出し先(source.php)を同じウェブサイトに同梱しているのでこの制限にはあたらない。

が、もし仮に別サイトから呼び出すことを考慮する2場合、source.phpの文頭にheader()を追記して以下のように変更しよう。

source.php
<?php

header('Access-Control-Allow-Origin: https://example.com'); // この部分を追記。 https://example.comのところは呼び出し元のドメインを指定。

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */
    echo "function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    document.body.appendChild(block);
    /* ここまで */
};";

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'アクセス……拒否しますっ!!(>_<)';
}

?>

参考文献

http://pc110.club/php/%EF%BD%8Aavascript-hidden
http://labs.vividworks.jp/javascript-%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AF%E9%9A%A0%E8%94%BD%E3%81%99%E3%82%8B%E3%81%B9%E3%81%8D%E3%81%8B%EF%BC%9F-vol-4/
https://tenderfeel.xsrv.jp/mootools/705/


  1. 正確には「オリジン」というやつをを比較する。わからなければ、ドメインと同じものだと思っておけばいい。 

  2. 同じサーバーであっても、別のサブドメインから呼び出したいときには必要だ 

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

非同期のまえに同期処理を通してPromiseとasync/awaitを理解する

JavaScript といえば非同期処理はつきものだが、非同期や Promise に苦手意識を持つ人も多いのではないだろうか。

これらの最初の理解のハードルは結構高いと思う。私も理解できずに悶絶した。C言語のポインタよりむずくないか?。。。

この記事の前半では一旦非同期のことは忘れる。
まず記事前半は、同期処理をテーマに、コールバック, Promise, async/await について説明する。
記事の後半は、これらを非同期処理を交えて説明する。

Promise はよくわからないという方や、一度挫折した方などにぜひ読んでもらいたい。

対象読者

  • JavaScript の基本的文法を知っている。(調べればわかる)
  • 非同期処理, コールバック, Promise, async/awaitに苦手意識がある、よくわからない。

JavaScriptを1行も読んだことも書いたこともない人、プログラミングをしたことのない人は対象としない。
逆に少しでも読み書きできればオーケー、のつもり。

目指すところ

  • 同期処理と非同期処理の違いがわかる
  • (非同期処理を対象とした) 他の Promise, async/await の解説記事を読んで理解が進む

Step0 アロー関数

最近の JavaScript (ES6~) ではアロー関数という記法がある。
アロー関数がわからない人も、この記事出てくるので簡単に抑えておきたい。

// 従来の書き方
function sum ( a, b ) {
  return a + b;
}

// アロー関数
const sum2 = ( a, b ) => {
  return a + b;
};

// アロー関数 関数の中がreturn文だけのときは、{return}を省略できる
const sum3 = ( a, b ) => a + b;

// アロー関数 引数が1つのときだけ()が省略できる (0つ、2つ以上はダメ)
const twice = a => a*2;

本記事では、上のように書けるということだけわかれば大丈夫。

Step1 同期処理

まずは非同期のことはわすれて、とりあえず読み進めて欲しい。

Step1-1 コールバック (同期関数)

コールバックとは、関数自体を引数として与え、別の関数に実行してもらうしくみだ。
電話を折り返すことに由来して名付けられた。
由来の通り、関数自体を伝えて「あとで都合が良くなったら実行しておいて」と実行を押し付ける方式。

function callbackFunc() {
  console.log('callback');
}

function callFunc ( func ) {
  func();
}

callFunc( callbackFunc );

ふつう、関数callbackFuncを実行するならcallbackFunc()のようにするだろう。
しかし上記では括弧をつけずcallbackFunc を引数として渡している。

括弧をつけないことで、引数として関数自体を渡すだけでその場では実行されない。
後に callFunc 関数の中で、渡された関数callbackFuncを実行してもらっている。

この「関数自体を渡す」というのがコールバックの肝である。
コールバックとは(戻り値の)値渡しではなく、関数自体の参照を渡しているという表現もできる。

次のような書き方では全く意味が変わってしまうので注意。

callFunc( callbackFunc() );

これでは、callbackFunc 関数を実行し、その戻り値を callFunc 関数に引数として渡すという意味になってしまう。

繰り返しになるが、コールバックは「関数自体を渡して」「あとで実行してもらう」しくみである。

参考: Callback function(コールバック関数) MDN web docs

(補足) コールバックとアロー関数

ちなみに上述のコードはアロー関数を使って次のようにも書ける。

const callbackFunc = () => {
  console.log('callback');
};

function callFunc( func ) {
  func();
}

callFunc( callbackFunc );

さらに、一度変数に入れるのをやめると

function callFunc( func ) {
  func();
}

callFunc( () => {
  console.log('callback');
})

引数を指定する中で関数を定義してしまうのだ。
このように関数自体を引数で渡すとき(即ちコールバック関数を渡すとき)、アロー関数でシンプルにかける。

Step1-2 Promise (同期関数)

Promise は英語で「約束する」という意味だ。
名前の通り、あとで値を返すことを約束するような動作をする。(約束を破ることもある。)

Promise の状態

Promise には3つの状態がある。

  • pending ... 約束している状態(初期状態)
  • fulfilled ... 約束を守って値を返した状態
  • rejected ... 約束を破った状態

Promise オブジェクトはまず pending で始まり、あとで fulfilled や rejected に状態が変化する。

状態: pending

とりあえず約束してみる。

const promise = new Promise( (resolve, reject) => {
});
// 何もしない関数を、new Promise() に渡している。
console.log( promise );

pendingと表示されただろう。

ここでの変数promiseは、Promise の状態 pending といえる。

状態: fulfilled

次は fulfilled の状態を作ってみる。

const promise = new Promise( (resolve, reject) => {
  resolve();
})
console.log( promise );
// 実はresolve,rejectはそれぞれ、渡された(コールバック)関数を引数として受け取っている。

Promise resoleved と表示されただろう。これが fulfilled である。

実は状態 fulfilled は値を持つ。

const promise = new Promise( (resolve, reject) => {
  resolve('hello');
})
console.log( promise );

ここでの変数promiseは、Promiseの状態 fulfilled であり、値'hello'を持つといえる。

状態: rejected

rejected も fulfilled と同様に値を持つ。

const promise = new Promise( (resolve, reject) => {
  reject('hello');
})
console.log( promise );

ここでの変数promiseは、Promise の状態 rejected であり、値'hello'を持つといえる。

rejected で渡される値(オブジェクト)は Error オブジェクトだったりする。

const promise = new Promise( (resolve, reject) => {
  reject(new Error('error message'));
})
console.log( promise );

ここでの変数promiseは、Promise の状態 rejected であり、値に Error オブジェクトを持つといえる。

状態の変化

Promiseでは状態が変化する。
初期状態では pending であるが、のちに fulfilled や rejected になる。

const promise = new Promise( (resolve, reject) => {
  //この行が実行されるタイミングでは、変数promiseは状態pending
  if( true ){
    //この行が実行されるタイミングでも、まだ変数promiseは状態pending
    resolve('resolveされた!');
    //この行が実行されるタイミングでは、変数promiseは状態fulfilledで値'resolveされた!'を持つ
    return;
  }
  // ここから先は実行されない
  reject('rejectされた');
})

現在は同期処理を行っているので、fulfill または reject された状態に一瞬で変化してしまい、 pending の状態をみることはできない。

しかし厳密にはもともとは pending で、 resolve()を実行すると fulfilled に、 reject() を実行すると rejected に、それぞれ状態が移行する。

then/catch による Promise チェーン

さて、Promise には3状態あり、変化することがわかった。
変化すると何ができるのか? それをこの節で説明する。

Promise オブジェクトのメソッドに、then と catch がある。

これらはそれぞれ第一引数に関数をとり、Promise が fulfilled や rejected の状態になると引数関数を実行する。

const promise = new Promise( (resolve, reject) => {
  resolve('hello');
})

promise.then( arg => {
  console.log(arg); // ここではhelloが表示される
  console.log('then is called');
})
const promise = new Promise( (resolve, reject) => {
  reject('hello');
})

promise.catch( arg => {
  console.log(arg); // ここではhelloが表示される
  console.log('catch is called');
})

このように.でつないで then/catch メソッドを呼べば、それらを発火できる。

さらに、then/catch メソッドの戻り値に promise を与えてやれば、更に繋げられる。

const promise = new Promise( (resolve, reject) => {
  resolve('hello');
})

promise
  .then( () => {
    console.log('then is called');
    return Promise.resolve('resolve!');
  })
  .then( arg => {
    return Promise.resolve( arg + '!' );
  })
  .then( arg => {
    console.log(arg); // resolve!! と表示される。
  })

// Promise.resolve('resolve!'); は、
// new Promise( resolve => { resolve('resolve!') }); と同じ。

上述の通り、then メソッドの戻り値に Promise を渡すと、更に後ろに.then()を繋げられる。
(.catch()も繋げられる。)

このように、Promise が解決 (fulfill/reject) されたら.then()メソッドが発火し、
.then()メソッドがPromiseを返すと、解決されたらさらに後ろの.then()メソッドが発火し、、、

このように数珠つなぎに徐々に Promise が渡ることを Promise チェーンと呼ぶ。


ここまでで Promise を学んだ。
コールバックや Promise を使う理由は非同期関数にあるので、読者の皆様にはややこしいことをしているようにしか見えないかもしれない。

本当はこのあたりで非同期関数について説明し Promise のありがたみを理解していただくのもよいのだが、この記事はあくまで「まず同期関数で理解する。」ことが目的であり、非同期関数はもう少し後回しにする。


Step1-3 async/await (同期関数)

次は sync/await だ。

そのまえに説明すべきことが2つほどあるので補足。

補足: 即時関数

即時関数は定義と同時に実行する関数だ。
関数定義を括弧でくくると即時実行される。

const Hello = () => { console.log('hello') };
Hello();

// 上2行のコードは、次の行のコードと同じ。
( () => { console.log('hello') });

// アロー関数でなくても良い
( function () { console.log('hello') });

補足: Async 関数 (asnyc function)

関数定義の前にasyncとつけて定義する。
Asnyc 関数の中でのみ await が使える。

// 例
const arrowFunc = async () => {
  await promise;
}
async function func() {
  await promise;
}

改めて async/await (同期関数)

閑話休題。

async/await は Promise を生成する構文と言っていい。
先程の then を書かずともよくなる構文である。

前節の Promise のコードを再掲する。

const promise = new Promise( (resolve, reject) => {
  resolve('hello');
})

promise.then( arg => {
  console.log(arg) // ここではhelloが表示される
  console.log('then is called');
})

これを async/await に書き直すと

const promise = new Promise( (resolve, reject) => {
  resolve('hello');
})

(async () => {
  const arg = await promise;
  console.log(arg); // ここではhelloが表示される
  console.log('then is called');
})

このようになる。
then が消えたことがわかる。

(即時実行のasync関数を使っている。)


もう一つ前節のコードを再掲し async/await に書き換えてみる。

const promise = new Promise( (resolve, reject) => {
  resolve('hello')
})

promise
  .then( () => {
    console.log('then is called')
    return Promise.resolve('resolve!')
  })
  .then( arg => {
    return Promise.resolve( arg + '!' )
  })
  .then( arg => {
    console.log(arg) // resolve!! と表示される。
  })

async/await に書き換えると

const promise = new Promise( (resolve, reject) => {
  resolve('hello')
})

( async () => {
  let arg = await promise
  console.log('then is called')
  let arg = await Promise.resolve('resolve!')
  let arg = await Promise.resolve( arg + '!' )
  console.log(arg) // resolve!! と表示される。
})

今度は then がなくなったことで短く書けたことが伝わるのではないか。

awaitが現れると、Async 関数内のawaitより後ろの部分が全てthen()の引数として包まれる、といった見方もできる。

以上のように、async/await は Promise を簡潔に書く構文である。


余談: Promiseは必要か?

async/awaitで簡潔にかけるなら、Promiseなんて理解しなくて良いのでは?と思う方もいるだろう。
しかし今の所そうも行かないのだ。

複数のPromiseを同時に待つ処理をasync/awaitで書いてみる。

( async () => {
  await Promise.all( [ promise1, promise2 ])
})

うお、Promise出てきた。。。

コードの内容はおいておいて、Promise という単語が出てきたことに注目。
解説は省くが、async/await は Promise を完全には隠しきれていないのだ。

(気になる方はこの記事を読み終えてからPromise.allをみると良いだろう。


Step2 非同期処理

さてさて、ここまで来ればゴールは近い。
この節では今まで苦労して覚えた謎構文 Promise と asnyc/await のありがたみがわかるようになる。

Step2-1 同期関数と非同期関数

同期関数と非同期関数について説明する。

  • 同期関数とは、中の処理が完了するまで待ってから戻り値を返す関数のこと
  • 非同期関数とは、中の処理にかかわらず、すぐに戻り値を返してしまう関数のこと

JavaScript の代表的な非同期関数にsetTimeOut()がある。

次のようなコードで考えてみよう。

setTimeOut( () => {
  console.log('hello');
}, 1000);
console.log('world');

JavaScript は、普通は(同期関数は)、上から順番に1行ずつ実行される。

しかし上記のコードを実行するとworldが表示された後にhelloが表示される。
これはsetTimeOut()関数が非同期関数だからだ。

書き方を少し変えてみる。

function Hello() { // 1
  console.log('hello'); // 4
}
setTimeOut( Hello, 1000) // 2
console.log('world') // 3

さっきと同じ動作をするコードだ。

コンピュータの気持ちになってみると

  1. Hello 関数を定義するよ。Hello 関数は実行されたら'hello'と表示するよ。まだ定義だけで実行しないよ。
  2. setTimeOut 関数を実行するよ。Hello 関数を 1000ms 後に実行するとを登録するよ。登録するだけで、すぐに戻り値を返すよ。
  3. 'world'と表示するよ。

... しばらく (1000ms) 経って ...

  1. Hello 関数を実行するよ、'hello'と表示するよ。

このような順で動作する。
同期関数はその行で処理が停止するのに対し、非同期関数はすぐに次の行が実行される。

Step2-2 コールバック (非同期関数)

先程の例で非同期関数を実現してるのがコールバックだ。

あとで実行して欲しい関数を引数で伝えておいて、ときが来たら実行する。

やりたいことはコールバックで実現できるものの、何重にも重なると次のようなコードにになってしまう。

setTimeOut( () => {
  setTimeOut( () => {
    setTimeOut( () => {
      setTimeOut( () => {
        setTimeOut( () => {
          setTimeOut( () => {
            console.log('6s later');
          }, 1000);
        }, 1000);
      }, 1000);
    }, 1000);
  }, 1000);
}, 1000);

console.log('これはすぐに実行される');

コールバック関数を呼ぶたびにネストが深くなってしまい読みづらい。

俗に言うコールバック地獄である。
たとえばこの例だと、どの秒数がどの setTimeOut に対応するのかわかりづらい。

(上記の例は全て一つの setTimeOut にまとめられるが)
実際には次のような状況が考えられる。

  1. サーバと通信して、記事のリストをとってくる。
  2. 記事のリストから該当の記事を探して、再度サーバと通信して本文をとってくる。

このように、複数の非同期処理が数珠つなぎになることもあるだろう。

数珠つなぎ、、、

Step2-3 Promise (非同期関数)

そう、数珠つなぎならさっきの Promise チェーンと相性が良い。

さっきの6秒待つ処理も

// 事前に Promise 関数を作っておく。
// ライブラリなどで用意されていたりするので、Promise を使う側は作る必要はない。
function setTimeOutPromise(time){
  return new Promise( resolve => {
    setTimeOut( resolve, 1000);
  });
}

setTimeOutPromise(1000)
.then( () => 
  setTimeOutPromise(1000)
).then( () =>
  setTimeOutPromise(1000)
).then( () =>
  setTimeOutPromise(1000)
).then( () =>
  setTimeOutPromise(1000)
).then( () =>
  setTimeOutPromise(1000)
).then( () => {
  console.log('6s later');
})

console.log('これはすぐに実行される');

ネストが解消されて、引数もコンパクトになって見やすくなった。

Step2-4 asnyc/await (非同期関数)

さらに async/awaitで書き直すと

// 事前に Promise 関数を作っておく。
// さっきと同じ。
function setTimeOutPromise(time){
  return new Promise( resolve => {
    setTimeOut( resolve, 1000)
  })
}

( async () => {
  await setTimeOutPromise(1000);
  await setTimeOutPromise(1000);
  await setTimeOutPromise(1000);
  await setTimeOutPromise(1000);
  await setTimeOutPromise(1000);
  await setTimeOutPromise(1000);
  console.log('6s later');
})

console.log('これはすぐに実行される');

これは見やすい!
非同期関数を同期的に書けるようになった。

await の行で停止しているかのように動作する。

さいごに

本記事を読み次の2つを知れば、他の記事が格段に読みやすくなるだろう。

  • 同期処理と非同期処理の違い
  • 同期処理でPromiseがどういう動作をするか

これからは「コールバック地獄を解決するために Promise チェーンがある」「 async/await は Promise の生成だ」などと書かれた他の記事も読めるのではないだろうか。

この記事を完全に理解できなくても、 読者の皆様はこれから JavaScript の非同期処理を深める土台ができているのではないかと思う。

ここまでの長文に付き合いいただきありがたい。

以上。

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

JavaScriptを用いて非同期通信③(doneメソッド)

ajaxと必ずセットでついてくるメソッド

結論、done,failメソッドです。
簡単に説明するとajaxに書いてあるコードが成功したときはdone書いてあるときのコードが、
失敗したときはfailのコードになります。

以上でajaxについては以上です。
次回からajaxに書く中身について具体的にどのようなものがあるのかを示していきます。
非同期通信完了まで後もう一歩!!

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

はじめてのThree.js 7章 日記

はじめてのthree.js

新たに知った知識

  • sizeAttentionはカメラの位置関係なく現れる大きさ
  • canvas要素で見た目を変更できる
  • THREE.PointsはfrustumCulledというプロパティがあり、パーティクルが画面外に出ると描画しないのでパフォーマンス向上する
  • particleで応用する際はmapプロパティにtextureを追加するのを忘れずに

気づいたこと

  • pointめっちゃ楽!

wow moment

まだ解決していない点

  • パーティクル、ポイントクラウド、スプライトの違いはなんとなく分かったけど説明するとなるとうーんってなる

7章はこちら

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