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

質問です。

初めまして。
エンジニアではないのですが、独学でHTML,CSS,javascriptの知識はほんの少しだけあります。

現在、360度画像をページに挿入したいと思いローカル環境で作成している途中なのですがエラーが出てしまい悩んでいます。

【作業状況】
・マシン:MacBook Air(10.15.5 ,メモリ8G ,)
・ブラウザ:Google Chrome(83.0.4103.116)
・テキストエディタ:atom(1.48.0)
・Google VR viewのスクリプトをHTML内で読み込み
・<div id="vrview"></div>を配置済み
・jsは別ファイルを作成してHTMLから読み込み

他の方々のQiita記事や他サイトも調べて、ローカル環境で自分でコードを書いてみたのですが、
用意した360度画像(4096×2048 , jpg形式)を読み込めず、エラーになってしまいます。
スクリーンショット 2020-07-12 19.35.08.png

原因を突き止めるべく以下の手順を行ってみたのですが、解決には至りませんでした。

①他の方の360度画像に関するQiita記事内に記載のあるURL(https://〜 で始まるもの)を試しにjsの「image:'',」の中に入れてみる
→二つとも表示された
 →サーバーにアップロードした画像は読み込める

②自分で用意したサンプル画像をWordPressなどにアップロードしてそのURL (https://noriharu.files.wordpress.com/2020/07/sample.jpg) を「image:'',」の中に入れてみる
→エラー
 →URLが正しくない?
 →WordPressっていうのが原因?
スクリーンショット 2020-07-12 23.48.54.png

③<img>タグを記述してサンプル画像を読み込んでみる
→画像は表示された
 →パスは間違っていない
  →Google VR viewはローカルファイルは読み込まない?

①〜③の工程を経て以下の疑問に辿り着いたのですが、これは正しいのでしょうか。
・Google VR viewはローカルファイルの画像は読み込めない?
・新しくサーバーを契約して画像をアップロードする必要がある?

ローカルのフォルダから360度画像をGoogle VR viewで読み込む方法があれば知りたいと思うのですが、そもそもそれは可能なのでしょうか。

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

JavaとJavaScript 間違えられやすい問題

はじめに

ちょっとキニなることを叫びます。
たぶん、これを読む皆さんも
おそらく聞いたことがあると思います。


結論から言います

JavaとJavaScriptは違います!!!


なんで間違えてしまうん

単純に「勉強不足」と片づけてしまえれば
楽なのですが
なぜか、頑なにJavaScriptを「Java」と
発言してしまう方がチラホラいます。


なぜこうなるのか

JavaScriptという言語自体が
Java開発に参画していたSun Microsystems社とともに
開発された言語だった為


でもさあのさ

うんうん、わかるよわかるよその気持ち
ホンマのIT技術者ならばこれくらいの違いは
わかって当然だよね。


それでもわからない人がいる

なんでや!!
ITの一般常識だと思っていますが
そもそもITに詳しくない人がIT業界には
一定数存在しています。(それはそれでおかしい)


Javaの有償化による大きな勘違い 事例

もともと、職場には
「JavaScript」を使ったアプリがありました。
Javaが有償化された時
上記のアプリを活用するにあたり
有償化になるけど大丈夫か
などという質問をいただきました。


事例の前提

要件定義や仕様の段階で
Javaを使っていないことは既にお断り済


リテラシーの問題か?

それでも「Java」と入っているので
何をどう勘違いしたのか
たびたび質問される方がおります。
こればかりはもう勉強してもらうしかないですね。


真に日本でエンジニアと呼ばれる人たち

おそらく
毎日勉強してらっしゃる方々だと思います。
新しい情報にしっかりキャッチアップして
進化していくことがエンジニアに重要なことだと
思います。

過激派っぽい発言をするのであれば
そうでなければエンジニアではない。
エンジニアと名乗る方がいても
私の思う定義から外れれば
自分と同じ土俵には立っていない。
そう思います。


おわり

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

MVCについて(100 days of code)

はじめに

100 days of code 8日目。
今日はMVCについて復習した内容を簡単に整理していきたいと思います。

MVCとは?

MVCとは、Model・View・Controllerの頭文字を取ったものを指しています。
MVCの特徴を1つずつ見ていきます。

Model(モデル)=システムのビジネスロジックを担当。
View(ビュー)=実際にブラウザに表示したり、入力する機能の処理を担当。
Controller(コントローラー)=ユーザーの入力に基づき、ModelとViewを制御する役割を担っている。

終わりに

普段MVCのような概念を復習する機会がなかったので、短い言葉でまとめてみました。
忙しいときは短く終わらせることもあります。
毎日更新するのは骨が折れる作業ですが、頑張りたいと思います。
明日も頑張ります。

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

【JavaScript】Null合体演算子と(??)とオプショナルチェイニング演算子(.?)

はじめに

JavaScriptの言語使用は、Ecma Internationalによって定められており201f年からはECMAScriptとして毎年改定されています。

ECMAScript2020の仕様としてNull合体演算子(??)とオプショナルチェイニング演算子(.?)が追加されました。(その他にもいくつかの機能が追加されています。詳細はこちらのリンクにまとめられています。

Null合体演算子とオプショナルチェイニング演算子は、どちらもnullableな変数に安全にアクセスするという似たような目的を持っており、かつググラビリティが低いという特徴を持っています。

現時点(2020年)で最新の仕様であり、あまり現場で見かけることがないかもしれませんが、この2つの演算子の特徴を見ていきましょう。

Null合体演算子

Null合体演算子は、演算子の一種で左辺がnullまたはundefinedのときに右辺の値を返し、それ以外のときには左辺を返します。
C#、Swift、PHPなどにも採用されているため見覚えがある人も多いかもしれません。

構文

A ?? B
let text;

const title = text ?? "タイトル";
console.log(title); // タイトル

これは以下の構文と同意義で、シンタックスシュガーといえます。

let text;

const title = text != null ? text : "タイトル";
console.log(title); // タイトル

OR(||)演算子との違い

Null合体演算子導入前までは、同じような機能を提供するために、OR(||)演算子が使われていました。

let text;

const title = text || "タイトル";
console.log(title); // タイトル

この結果を見ると、Null合体演算子とOR演算子どちらを使っても変わらないように思わます。

しかし、OR演算子はnullundefinedに限らずfalsyな値0, '', NaN, null, undefinedのときに左辺の値を返すという特徴があります。

これは、例えば0をを正しい値として扱いたいときに思わぬ結果を返す可能性があります。

const value = 0;

const a = value ?? "記録がありません。";
const b = value || "記録がありません。";

console.log(a); // 0
console.log(b); // 記録がありません。

短絡評価

Null合体演算子は、AND演算子やOR演算子のように短絡評価されます。
右辺の値がnullまたはundefinedでないことが証明されたとき、右辺の値は評価されません。

const a = "a" ?? console.log("a");
const b = null ?? console.log("b"); // b

AND演算子やOR演算子と同時に使えない

Null合体演算子を、AND演算子やOR演算子と直接同時に使おうとするとsyntax errorが発生します。

const a = 0 || null ?? 'a'
const b = 1 && null ?? 'b'

// error: Uncaught SyntaxError: Unexpected token '??'
// const a = 0 || null ?? 'a'

Null合体演算子をAND演算子やOR演算子と使用したいときには、カッコをつけて明示的に優先順位をつける必要があります。

const a = (0 || null) ?? "a";
const b = (1 && null) ?? "b";

console.log(a); // a
console.log(b); // b

使用例

Null合体演算子は、nullableな要素を使用する際に初期値を代入したいときに役に立ちます。

例えば、次の例はクエリパラメータにusernameが存在すればそれをページのタイトルとし、存在しないときには初期値を代入します。

const params = (new URL(document.location)).searchParams;
document.title = params.get('username') ?? 'Default Title';

オプショナルチェイニング演算子

オプショナルチェイニング演算子(.?)は、ググりづらい上に名前まで長くて覚えづらいという厄介な演算子ですが(?)なかなか役にたちます。

オプショナルチェイニング演算子は、nullableなオブジェクトに対して安全にアクセスするための演算子です。これは、プロパティにアクセスするときに.の代わりに.?を使用して、オブジェクトがnullまたはundefiendだった場合、実行時エラーを吐く代わりにundefinedを返します。

構文

obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)
let user;

console.log(user.name);
// error: Uncaught SyntaxError: Unexpected token '?'
// console.log(user.?name)

console.log(user?.name);
// undefined

オプショナルチェイニング演算子をネストした演算子に使う

オプショナルチェイニング演算子は、何個でも使用できるため次のようにネストしたプロパティに対しても使用することができます。

const obj {
  first: {
    second: {
      third: 'hey!'
    }
  }
}

obj?.first?.secont?.third

オプショナルチェイニング演算子を使用した関数呼び出し

オプショナルチェイニング演算子は安全に関数を呼び出すために使用することができます。
関数呼び出しには次のような構文に従います。

obj.someFunction?.() 

しかし、この構文が有効に働くのはsomeFunction()が存在したいときだけです。以下の例のように、存在はするがメソッドではないプロパティに対して使用すると例外をスローします。

const obj = {
  someFunction: "1",
};

obj.someFunction?.();
// error: Uncaught TypeError: obj.someFunction is not a function
// obj.someFunction?.();

ブラケット表記法に使用する

const obj = { 
   first: 1,
   second: 2
}

const page = 'first'

const content = obj?.[page]

配列の要素にアクセスするときに使用する

const array = ["first", "second"];
const notArray = "1";

console.log(array?.[0]); // first
console.log(array?.[3]); // undefined
console.log(notArray?.[0]); // undefined
console.log(notArray?.[1]); // undefined

使用例

オプショナルチェイニング演算子は、APIから取得した要素や、DOMから取得した要素など、nullundefinedで返されるオブジェクトにアクセスするときに役に立ちます。

例えば、次の例はUser型のオブジェクトの配列からidをもとに特定のユーザーを見つけ出し名前を表示しようとしますがfind()メソッドはundefinedを返す可能性があります。

if (user == null)のように存在確認してからプロパティにアクセスすることもできますが、オプショナルチェイニング演算子を使えば簡潔に記述することができます。

const users = [
  {
    id: 1,
    name: "Aron",
  },
  {
    id: 2,
    name: "Abel",
  },
  {
    id: 3,
    name: "Michael",
  },
];

const user = users.find((user) => user.id === 4);
console.log(user?.name);

おわりに

WikipediaのNull合体演算子のJavaScriptの項目誰か更新しといてください。

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

javascript スクロール機能

JavaScriptでスクロール機能をつけたいときのコードです。

<!DOCTYPE html>




※adjustという変数は調整の意味です。

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

javascript スクロール機能をつけるとき

JavaScriptでスクロール機能をつけたいときのコードです

htmlタグの一番下に以下のコードを記入します。

<script>
$(function () {
      $('a[href^=#]').click(function () {
        var adjust = -50;
        var speed = 400;
        var href = $(this).attr("href");
        var target = $(href == "#" || href == "" ? 'html' : href);
        var position = target.offset().top + adjust;
        $('body,html').animate({scrollTop: position}, speed, 'swing');
        return false;
      });
    });
</script>

※adjustという変数は調整の意味です。

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

Chrome拡張機能のイベントぺージとストレージ有効期限

1行概要

Chrome拡張機能のイベントページにおける①Web Storage、②Session Storage、③Cookieの情報有効期限についてメモしました。

はじめに

メモリの大量消費で悪名高いGoogle Chromeですが、その原因の一つに拡張機能の常駐があることは周知の通りです。
それに対し、GoogleはChrome拡張機能のイベントドリブン化を推奨しているというのもご存じの通りかと思います。

Background scripts are registered in the manifest under the "background" field. They are listed in an array after the "scripts" key, and "persistent" should be specified as false.

Chrome拡張機能において、イベントドリブンな処理を記述したものがイベントページです。
manifest.jsonで宣言するだけなので、イベントドリブンを指定したものというのが正しい表現かもしれませんが。

manifest.json
"background": {
  "scripts": ["background.js"],
  "persistent": false //trueのときバックグラウンドページ, falseのときイベントページ
}

Chrome拡張機能のイベントページは一定時間経過するとメモリを解放します。
では、イベントページにおける各ストレージの挙動と情報の有効期限はどのようになっているのでしょうか。

Tips
- Manage Events with Background Scripts - Google Developers
- Chrome機能拡張のイベントページについて

各ストレージの挙動

①Web Storage

Chrome拡張機能においては、Local Storageとchrome.storageの2つ、さたに後者はsync, local, managedという3つのプロパティに分別され、この中からいずれか任意のWeb Storageを選択することになるかと思います。
valueの中身やjson操作有無、同期/非同期などの違いはありますが、本質的には同様の動作だったのでLocal Storageを例に確認します。

background.js
 var data = '';

 chrome.browserAction.onClicked.addListener(function() {
   data = localStorage.getItem('key');

   console.log(data); //(空白) -> a -> aa -> ~~バックグラウンドページ(無効)~~ -> aaa ->...

   localStorage.setItem('key', data + 'a');
});

挙動としては、ブラウザアクションボタンをクリックする度に文字列dataaを足してWeb Storageにセットしています。
そして、『バックグラウンドページ(無効)』つまりメモリ解放した後も変わらず+ 'a'されて出力されます。

つまり、Web Storageは消去操作を行わない限り永久的に情報を保持することができます

②Session Storage

想定と違う動作をしたのがSession Storageです。

background.js
 var data = '';

 chrome.browserAction.onClicked.addListener(function() {
   data = sessionStorage.getItem('key');

   console.log(data); //(空白) -> a -> aa -> ~~バックグラウンドページ(無効)~~ -> (空白) -> a ->...

   sessionStorage.setItem('key', data + 'a');
});

基本的にSessionはセッションが切れるまで、つまりブラウザを閉じるまで情報を保持すると解釈していました。
しかし、『バックグラウンドページ(無効)』時にセッションが切れていたのです。

つまり、Session Storageは無効化と同時に情報を喪失します

③Cookie

もはや動作を確認するまでのないですね。

CookeiもSession Storageと同様に、無効化と同時に情報を喪失します。

課題

そこで課題となったのが、無効化されても情報を保持して、Chrome起動ごとに情報をリセットしたいというケースの場合です。
本来であればセッションで情報を保持して、Chromeの開閉で情報のリセットをしたかったのですがそれができないということが判明したのです。

理論的に言えば、「Chromeが閉じられるとき」もしくは「Chromeが開かれたとき」に情報をリセットするという処理を入れればよいわけです。しかし、結果から言えばそのような処理を実装することはできませんでした。
beforeunloadやらruntimeやらをしましたが、私が意図する動作は実現できませんでした。

おわりに

Chrome拡張機能のイベントページにおける①Web Storage、②Session Storage、③Cookieの情報有効期限は、それぞれ永久的、無効化まで、無効化までという結果でした。
誤解を恐れずに言うと、Chrome拡張機能の無効化はChromeを閉じるときと同等の動作をするということになるかと思います。

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

純粋関数

プログラミングの基礎についての他の記事はコチラ

純粋関数について説明する。

純粋関数の特徴は以下である。

  • 引数が同じ場合、常に同じ返り値となる。(参照透過性)
  • 副作用が発生しない

例えば、下記のようなadd関数は純粋関数である。

function add(a, b) {
  return a + b
}

a=1かつb=2の場合、必ず3が返る保証があるためである。

一方でランダムな値を返す関数やAPIへのアクセスをしている場合は、毎回結果が違うことから純粋関数ではなくなる。

では下記の関数はどうか。

function increment(obj) {
    obj.num = obj.num + 1

    return obj
}

obj={ num: 1 }の場合、必ず{ num: 2 }が返る保証がある。

しかし、この関数は副作用を持つ。

JavaScriptの仕様として、プリミティブな値以外はミュータブルである。その為、引数のobjのメンバへの変更は呼び出し元の変数を書き換えてしまう。

function increment(obj) {
    obj.num = obj.num + 1

    return obj
}

var origin = { num: 1 }

var result = increment(origin)

console.log(origin) // { num: 2 } 呼び出し元の変数に変更が入ってしまっている
console.log(result) // { num: 2 }

この関数は、関数の外への影響 = 副作用を持っている。

よって純粋関数ではない。

これ以外の副作用の例として、外部デバイス・ネットワーク・ファイルシステムとのIOや、グローバル変数・スコープ外変数などの共有変数の参照または変更がある。

純粋関数を用いるメリットは、テスト性とコードのトレーサビリティである。

関数が関数外への影響(副作用)を持つということは、関数外の値やデータベースの状態を考慮してテストし、なおかつコードを追わなければいけなくなる。

関数の中に影響をとどめる(=ローカリティを高める)ことは、純粋関数の使用や結合度の低さと関連している。

また、多くの有用なライブラリは、ユーザーが純粋関数の実装のみで目的を達成できるように調整されていることが多い。

IOによる副作用を除去する方法として、依存性注入のテクニックを利用することもできる。

純粋関数ではIOが発生しないことからパフォーマンスが良くなる傾向があるが、たまに計算量の多い処理がある。

その場合、「引数が同じ場合、常に同じ返り値となる」特徴を利用して、メモ化をすることができる。

メモ化を行うと、引数と結果のセットがキャッシュできるため、2回目以降の呼び出しはキャッシュの参照のみを行えばよくなる。

こうしたことから、純粋関数は非常に有用である。

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

高階関数とカリー化

プログラミングの基礎についての他の記事はコチラ

高階関数について説明する。

高階関数とは、端的に言えば関数を返す関数のことである。

JavaScript での例を下記に示す。

var add = a => b => a + b

このadd関数は、下記のように実行できる。

var increment = add(1)

このように関数の実行によって、新しい関数を作ることができる。

新しく作成された increment 関数は add 関数の特定のケースである。(この場合、a=1に束縛される)

特定のケースに対するモジュールの作成は、より一般的には特化と呼ばれる。

しかし、実際のプログラムの作成においては

var increment = n => n + 1

というように特化された関数が先に考案されることが多い。

そこから汎化(特化の逆)を行うには、具体的な値(ここでは1)をパラメータにした高階関数を作ればよい。

var add = a => n => a + n

では、既に下記のような関数があった場合のことを考えてみよう。

var add = (a, n) => a + n

この場合、add(1)の結果はundefinedとなってしまう。

よってカリー化が必要になる。その為の関数がlodash には用意されている

var add = _.curry((a, n) => a + n)
var increment = add(1)

console.log(increment(0)) // 1
console.log(add(1, 2)) // 3

incrementは1ずつ数値=データを増加させる関数としたい。

そのため、1を束縛させるパラメータがデータより先に来なくてはならない。

よって高階関数を作成する際は、一般的にdata-last(データが最後)アプローチが取られる。

なお、ReasonMLを利用した場合は、複数の引数を持つ関数は自動的にカリー化される

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

D3.jsで円とか線とか描画する

目的

D3.jsで小学生の娘&息子とお絵描きしながらその過程で作成したモノをアップしました。
データを修正しながら、座標や配列について学習してもらいました。

円を描く

2020-07-12 173902.png

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>D3.js example</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
</head>

<body>
    <div class="container vh-100">
        Data-Driven Documents Example
        <svg id="target" width="100%" height="90%" style="background-color: blue"></svg>
    </div>
</body>
<script>
    var svg = d3.select("#target");

    var w = $("#target").width();
    var h = $("#target").height();

    d3.select("div").append("p").text("svg width x height : " + w + " x " + h);

    // 入力データは[x座標、y座標、値(円の大きさ)]
    const dataset = [
        [1, 1, 50], [1, 2, 25], [1, 3, 10], [2, 1, 50], [2, 2, 25], [2, 3, 10], [3, 1, 50], [3, 2, 25], [3, 3, 10]
    ];

    // 入力ドメインと出力レンジをマッピングさせたスケール関数を定義
    // padding の分だけレンジを狭める
    const padding = d3.max(dataset, function (d) { return d[2]; });

    const xScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function (d) { return d[0] + 1; })])
        .range([padding, w - padding]);

    const yScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function (d) { return d[1] + 1; })])
        .range([padding, h - padding]);

    // 円を描画
    svg.selectAll("circle")
        .data(dataset)
        .enter()
        .append("circle")
        .attr("fill", "white")
        .attr("cx", function (d) {
            return xScale(d[0]);
        })
        .attr("cy", function (d) {
            return yScale(d[1]);
        })
        .attr("r", function (d) {
            return d[2];
        });
</script>
</html>

線を引く

SVG の Line 要素で8本の線を描画しています。

2020-07-12 203008.png

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>D3.js example</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
</head>

<body>
    <div class="container vh-100">
        Data-Driven Documents Example
        <svg id="target" width="100%" height="90%" style="background-color: blue"></svg>
    </div>
</body>
<script>
    var svg = d3.select("#target");

    var w = $("#target").width();
    var h = $("#target").height();

    d3.select("div").append("p").text("svg width x height : " + w + " x " + h);

    // 入力データは[x座標、y座標、値(円の大きさ)]
    const dataset = [
        [1, 1, 5], [1, 2, 5], [1, 3, 5], [2, 1, 5], [2, 2, 5], [2, 3, 5], [3, 1, 5], [3, 2, 5], [3, 3, 5]
    ];

    // 入力ドメインと出力レンジをマッピングさせたスケール関数を定義
    // padding の分だけレンジを狭める
    const padding = d3.max(dataset, function (d) { return d[2]; });

    const xScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function (d) { return d[0] + 1; })])
        .range([padding, w - padding]);

    const yScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function (d) { return d[1] + 1; })])
        .range([padding, h - padding]);

    // 円を描画
    svg.selectAll("circle")
        .data(dataset)
        .enter()
        .append("circle")
        .attr("fill", "white")
        .attr("cx", function (d) {
            return xScale(d[0]);
        })
        .attr("cy", function (d) {
            return yScale(d[1]);
        })
        .attr("r", function (d) {
            return d[2];
        });

    // 線の入力データは始点と終点の座標
    const dataset2 = [
        [1, 1, 1, 2], [1, 2, 1, 3], [1, 3, 2, 3], [2, 3, 2, 2], [2, 2, 2, 1], [2, 1, 3, 1], [3, 1, 3, 2], [3, 2, 3, 3]
    ];

    // 線を描画
    svg.selectAll("line")
        .data(dataset2)
        .enter()
        .append("line")
        .attr("fill", "none")
        .attr("stroke", "white")
        .attr("stroke-width", 10)
        .attr("x1", function (d) {
            return xScale(d[0]);
        })
        .attr("y1", function (d) {
            return yScale(d[1]);
        })
        .attr("x2", function (d) {
            return xScale(d[2]);
        })
        .attr("y2", function (d) {
            return yScale(d[3]);
        })
        ;

</script>

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

JavaScriptデバッグテクニック:「debugger」

サンプル.js
function hoge(){
  const test = 1
  debugger
}
hoge() // debuggerで止まってソースパネルが開く

debugger - JavaScript | MDN

debugger ステートメントは、ブレークポイントの設定のような任意の利用可能なデバッグ機能を呼び出します。デバッグ機能が利用可能ではない場合、このステートメントは効果がありません。

debbugerのいいところ

昨今はトランスパイルが流行りなので、コンパイル済みコードにブレークポイントを仕掛けるのは面倒ですが、
この方法ならイベントで起動するメソッドなど、どんなところでも簡単にブレークポイントを貼れます。

真の力

image.png
止まっている時にconsoleタブに戻ることができます。
この時、スコープは止まっているメソッドの中になります。

なので、現在のスコープにある変数に直接操作したりできるので
「コードを書いて保存して動かして動作を確認して、駄目ならまたコードを書き換えて…」みたいな煩わしさから開放されます!

是非活用してみてください。

その他のデバッグ術

[サーバーサイドAPIデバッグ術]ブラウザのリクエストを簡単にリトライしてサーバーの挙動を見る方法 - Qiita

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

[JavaScriptデバッグテクニック]メソッド実行中の変数の中とか見たくない?

それ debugger でできるよ
開発者ツールを開いていればこのコードで止まってくれます。
(開発者ツールを閉じていると特に何も起きません)

サンプル.js
function hoge(){
  const test = 1
  debugger
}
hoge() // debuggerで止まってソースパネルが開く

debugger - JavaScript | MDN

debugger ステートメントは、ブレークポイントの設定のような任意の利用可能なデバッグ機能を呼び出します。デバッグ機能が利用可能ではない場合、このステートメントは効果がありません。

debbugerのいいところ

昨今はトランスパイルが流行りなので、コンパイル済みコードにブレークポイントを仕掛けるのは面倒ですが、
この方法ならイベントで起動するメソッドなど、どんなところでも簡単にブレークポイントを貼れます。

変数の中とか見たいしメソッドも実行したい

タイトルなし.gif

止まっている時にconsoleタブに戻ることができます。
この時、スコープは止まっているメソッドの中になります。

なので、現在のスコープにある変数に直接操作したりできるので
「コードを書いて保存して動かして動作を確認して、駄目ならまたコードを書き換えて…」みたいな煩わしさから開放されます!

是非活用してみてください。

その他のデバッグ術

[サーバーサイドAPIデバッグ術]ブラウザのリクエストを簡単にリトライしてサーバーの挙動を見る方法 - Qiita

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

独力で一からJavaScriptコードを書くには?

独力で一からJavaScriptコードを書くには?

概要

Q&Aサイトの頻出質問に以下があります。

「他人のコードを改変することなら出来るのですが、自分で一からコードを書くことが出来ません。どうすれば自分ひとりの力で書けるようになりますか。」

その回答を体系的にまとめたのが本記事になります。

[必要条件] 論理的思考

独力で「出来る人」と「出来ない人」の性質を比較表にしました。

比較項目 出来る人 出来ない人
現象認識の単位 分解して、最小単位で認識(分割統治法) そのままの状態でおおざっぱに認識
主観/客観 客観的 主観的
考察の手法 論理的 イメージ
用語の使い方 正式用語以外はNG 何となく伝わればOK
コーディング前の準備 要件/アルゴリズム等の必要情報を予め調べる 準備不要。とりあえず、コードを書く。
コードの完成度 コードの動きを100%理解しなければNG 動けばOK

プログラミング言語を学習する過程で論理的思考は必ず必要となりますが、その過程で、思考が機械的になる傾向があります。

プログラミング言語が相手にするには「コンピュータ」という名の機械であり、機械はイメージを理解できませんし、何となく書いた言語も解釈出来ません。

  • 機械が解釈できる「厳密で論理的なコード」を書く為には「厳密で論理的な思考」が必要となります
  • 用語も、正式用語を使わなければ、正確に理解できず、コードを組む前の考え方に組み込めません

思考を機械に近づける事(※1)がプログラミング上達のポイントなのかもしれません。

(※1)
その為か、プログラマを「冷たい人間だ」と評される事があります。
気持ちやイメージを徹底的に排除し、論理だけを重んじる姿勢を「冷たい」と評していると思われます。
が、プログラミングに必要だからそうしているのであって、人間的に冷たいわけではないと私は思います。

(1) 問題を分解する

  • 問題が起きたとき
  • 文章を読むとき
  • コードを読むとき

全てにおいて、分解して「最小限の情報量」におさめます。
複雑な内容でも分解して、単純情報になれば、自分一人の力で理解できるからです。

1 + 4 * 3 - 2;

この四則演算式を人間が解く場合、「二項演算式を部分的に解いて、少しずつ計算していく手法」を取る方がほとんどだと思います。

1 + 4 * 3 - 2;
1 + 12 - 2;
13 - 2;
11;

同じ作業を、全ての情報において、実践します。
この手法を分割統治法といいます。

(2) 再現可能な最小限のコード

質問に対して、「問題を再現可能な最小限のコードを掲示して下さい」という指摘がされることがありますが、これも「分割統治法」です。

「再現可能な最小限のコード」を書く為に、少しずつ小さなコードに変化させていく過程で、問題点が単純化され、自力で原因を発見して、解決に至るケースは珍しくありません。
私は何度も経験しています。

(3) 仕様書を読む

ある内容をWeb検索で調べ、複数の情報ソースから情報Aと情報Bを発見したとします。
情報を精査したとき、情報Aと情報Bで矛盾が出ることがあります。
人が書く情報には勘違いや記憶違いで誤りを発信してしまうこともあります。

そんなときに100%の信頼性を持つのが仕様書です。
JavaScriptは特異な言語で「JavaScript」という名の仕様書はありません。
「ECMAScript」「DOM」「HTML Standard」等の複数の仕様書から成り立っており、調査前にどの仕様書を調べるのかを確かめておく必要があります。

慣れるまでは、MDNを入り口に「仕様書」の場所を確かめると良いと思います。
例えば、下記ページの「仕様書」のセクションで「ECMAScript (ECMA-262)」にリンクされています。

(4) ブラウザのDevelopper Toolsで調べる

仕様を調べても、ブラウザに実装されていなかったり、バグが混入されている事があります。
実際に、ブラウザで実行してみるのは重要です。

いまどきのブラウザはDevelopperTools(開発者ツール)でブラウザの動きを追跡できます。

JavaScriptコードを書いた時に「動作対象ブラウザ」は決めているはずなので、対象ブラウザの開発者ツールの機能は把握しておく必要があります。


基本的にはリファレンスを読みながら、使い方を覚えていくことになりますが、コンソールを使うこなす上で評価値という概念が重要になります。
例えば、コンソールに 1 + 2 を入力した場合、

1 + 2
3

このように、1 + 2 の演算結果が次の行で返ってきます。
これを 1 + 2評価値を返す、と表現します。

式文(ExpressionStatement)は全て「評価値」が返ってくるので、覚えておくと便利でしょう。
コンソールにコードを入力すれば、console.log() を使わなくても、演算結果を得る事が出来ます。

なお、コンソールに console.log(1 + 2) を入力すると、

console.log(1 + 2);
3
undefined

3console.log(1 + 2) の実行結果によるものですが、undefinedconsole.log(1 + 2) の評価値です。
関数呼び出しコードにおいては、評価値は「関数の返り値」と等価となります。
「Console API」は標準化されている為、仕様書を読んでおくと、より深く理解できます。

(5) コーディング

プログラマは次の3つのstepを経て、コードを書いています。

  1. 要件定義
  2. アルゴリズム定義
  3. コーディング

「要件定義書」のようなきちんとした文書は残していないかもしれませんが、出来るプログラマは「要件は何か」「どのようなアルゴリズムを組むか」という思考を常にしているものです。

ここでは正道で「1 -> 2 -> 3」の順序で説明していますが、コードを書き始めてから「要件」を見直すパターンもあります。
しかし、要件を何も決めずにコードを書くことは出来ませんので、おおまかにでも要件を決めてコードを書いているはずです。
いずれにしても、「要件」があることは頭に入れておく必要があります。

(5-1) 要件定義

function sum (num1, num2) {
  return num1 + num2;
}

console.log(sum(1, 2));     // 3
console.log(sum("1", "2")); // "12"

後者を「仕様」と見なすか、「バグ」として受け入れるか、が「要件」の違いです。
「仕様」とみなす場合、このコードは

  • 「引数1」「引数2」に String 型を指定した場合、加算ではなく、文字列連結結果を返します

という「要件」を持つことになり、要件通りの挙動なので、不具合報告に対しても、「仕様」として「修正しない方針」で返答します。
対して、「バグ」として受け入れる場合、修正が必要で、例えば、以下の修正コードが考えられます。

function sum (num1, num2) {
  return Number(num1) + Number(num2);
}

console.log(sum(1, 2));     // 3
console.log(sum("1", "2")); // 3

この場合の「要件」は、

  • 「引数1」「引数2」はNumber 型に変換してから、加算を行います

になり、それがそのまま「仕様」となります。

「要件」と「仕様」は似ていますが、「要件」には

  • 期待される動作
  • 要求される動作

の意味があり、「コードを書く前に決定する仕様」のような意味になります。

(5-2) アルゴリズム定義

アルゴリズム(英: algorithm [ˈælgəˌrɪðəm])とは、「計算可能」なことを計算する、形式的な(formalな)手続きのこと、あるいはそれを形式的に表現したもの。コンピュータにアルゴリズムをソフトウェア的に実装するものがコンピュータプログラムである。人間より速く大量に計算ができるのがコンピュータの強みであるが、その計算が正しく効率的であるためには、正しく効率的なアルゴリズムに基づいたものでなければならない。

アルゴリズムは人間が理解しやすいようにフローチャートで表される事があります。
JavaScriptコードからフローチャートを生成する「js2flowchart.js」を見てみましょう。

例えば、demoで初期表示されたコードに関数呼び出しコードを加えた場合、

function indexSearch(list, element) {
    let currentIndex,
        currentElement,
        minIndex = 0,
        maxIndex = list.length - 1;

    while (minIndex <= maxIndex) {
        currentIndex = Math.floor((maxIndex + maxIndex) / 2);
        currentElement = list[currentIndex];

        if (currentElement === element) {
            return currentIndex;
        }

        if (currentElement < element) {
            minIndex = currentIndex + 1;
        }

        if (currentElement > element) {
            maxIndex = currentIndex - 1;
        }
    }

    return -1;
}

indexSearch([1,2,3,4,5], 3);

フローチャートをwhileループ回数分だけ印刷し、チャート上のそれぞれの動きを順番に追いかけて、各変数値を記入していけば、人間の目でも動きを追跡できますので、やってみてください。

indexSearch([1,2,3,4,5], 3); // 2

関数の返り値は 2 が正解ですが、フローチャートを使って、同じ答えを導きだせたでしょうか。

ここでは、フローチャートを使いましたが、フローチャートを作るのが目的ではありません。
(4) で「アルゴリズム定義後にコーディング」する過程を書きましたが、それは

  1. アルゴリズムを頭の中で作って動かす
  2. そのアルゴリズムで目的の動作が実現できるか頭の中で確認する
  3. コードを書く

の作業を表しています。

この作業は無意識下かもしれませんが、プログラマは皆やっています。
それをしないと、動くか動かないか分からないコードをあてずっぽうに書くことになってしまいます。
コードを書く前に「動く」と確信を持つには、事前に頭の中で動かす必要があります。

(5-3) コーディング

予め用意されている「ビルトイン関数」にもアルゴリズムはあります。
ここでは、比較的単純な「ECMAScript 5.1当時の Array.isArray」を例題にします。
(学習の為には、最新仕様の「ECMAScript 2020」を題材にすべきですが、アルゴリズムが複雑になったので、ここでは構造が単純な古い仕様を採用しています)

  1. If Type(arg) is not Object, return false.
  2. If the value of the [[Class]] internal property of arg is "Array", then return true.
  3. Return false.

このように、アルゴリズムは序列リストで表すことも出来ます。
(whileループも「2. に戻る」を使えば、表現可能です)
仕様書は序列リストでアルゴリズムを表すので、この表現に慣れておくと、仕様を解読しやすいでしょう。

このアルゴリズムをコードに変換してみます。

function isArray (arg) {
  // 1. If Type(arg) is not Object, return false.
  if (Object(arg) !== arg) {
    return false;
  }

  // 2. If the value of the [[Class]] internal property of arg is "Array", then return true.
  if (Object.prototype.toString.call(arg) === '[object Array]') { // [[Class]] 内部プロパティは Object.prototype.toString で確認する事が可能です
    return true;
  }

  // 3. Return false.
  return false;
}

これが、「アルゴリズム定義」から「コーディング」する流れ、です。

まとめ

前提条件の論理的思考を除くと、実作業は次の流れになります。

  1. (1)~(3) 概念/原理を理解する
  2. (4) 単純化した単機能からテストする
  3. (5-1) 要件定義
  4. (5-2) アルゴリズム定義
  5. (5-3) コーディング

一般のイメージとして「プログラマはキーボードをたくさん叩いてコードを書いている」と感じている方が多そうですが、実際には考察/調査している時間が大半を占めています。

コーディングの段階になれば、

  • これから書くコードの一つ一つを100%理解している
  • 「このコードを書けば、期待通りに動作する」という確信を持っている

という前提でコードを書きます。

コードが期待通りに動かず、前stepに戻る場合は、(1)からやり直し、次こそは「100%の理解(※2)」にしてからコーディングにのぞみます。

(※2)
「100%の理解」とはプログラマが持つ意気込みのようなものです。
「動くか動かないか分からないけど、コードを書いてみよう」な状態で、コードを書いてはいけません。
「このコードをかけば、こういう動作になる」という原理を理解した上でコードを書かなければ、不測の事態に対応できません。

「理解 -> 設計 -> コーディング -> 不具合発生 -> 始めに戻る」のサイクルはPDCAとよく似ています。

[補足] 分割統治法で再利用性を上げる

「分割統治法」で小さな単位にしたとき、

上記記事で while 文を「最も基本となる文」と紹介しましたが、while 文を使いこなせれば、他に紹介した全ての繰り返し処理を実現可能です。
そして、他に紹介した繰り返し処理

  • for
  • for-of
  • Array.prototype.forEach
  • for-in
  • Map.prototype.forEach

の「アルゴリズム」を理解できたとき、これらの処理の相互変換が可能となります。
アルゴリズムの理解は「メリット/デメリットの列挙」を可能とするので、取捨選択も自ずとできるようになります。

[補足] 式文(ExpressionStateMent)

(4) で触れた式文(ExpressionStateMent)の概念は以下の記事で説明しています。

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

【Nuxt/Rails】formDataで画像をPOSTしてCarrierWaveで保存した

Nuxt.jsからformDataで画像をPOSTして、Railsで画像を保存する(CarrierWaveを使ってます)際に地味に詰まってしまったので備忘録を残します。

Rails

Google Cloud Storageに画像を保存していまして、設定諸々は以下の備忘録に書いてます。

https://qiita.com/arthur_foreign/items/43da529ab3beb760ba4b

※そのため、CarrierWaveの設定周りは省略しました。

また、実装としては記事のサムネイルを保存することとします。

model

article.rb
class Article < ApplicationRecord
  belongs_to :user
  mount_uploader :thumbnail, ImageUploader
  serialize :thumbnail
end

controller

articles_controller.rb
class ArticlesController < ApplicationController
  def create
    article_form = ArticleForm.new(article_form_params)
    if article_form.invalid?
      render_422(article_form)
    else
      @article = article_form.save!
      render json: @article, status: :ok, serializer: ArticleSerializer
    end
  end
end

serializer

article_serializer.rb
class ArticleSerializer < ActiveModel::Serializer
  attributes :thumbnail
  belongs_to :user, serializer: UserSerializer
end

form

article_form.rb
class ArticleForm
  include ActiveModel::Model
  include Virtus.model

  attr_reader :thumbnail, :user_id

  attribute :thumbnail, String
  attribute :user_id, User

  def save!
    Article.create!(
      thumbnail: thumbnail,
      user_id: user_id
    )
  end
end

Vue/Nuxt

画像を送信する時のformDataのみ書いてます。

<script>
  methods: {
    async handleSubmit() {
      const formData = new FormData()
      const blob = new Blob([this.image], { type: this.thumbnailType })
      formData.append('article[thumbnail]', blob, this.thumbnailName)
      await this.postArticle(formData)
    },
  },
</script>

※画像の設定は別の備忘録に書こうと思います。

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

JavaScriptだけで定期的にデータを更新してくれるお洒落ダッシュボードを作る(no code編もあるよ!)

はじめに

データ分析って面白いけど、データ集めるのって大変ですよね。
ダッシュボードって見やすいけど作るの大変ですよね。
ということで、JavaScriptだけでデータを自動更新してくれるちょっと格好いいダッシュボードを作ってみます。

最初に謝っておきたいのは、ちょっとタイトル詐欺をしてしまっていて、完全なダッシュボードはここでは作りません。グラフの表示までで、それをどう並べるかはお任せになっています。。。

一応はグラフ1つですがダッシュボードの形式までは紹介します。グラフの表示さえできてしまえば、あとは並べるだけなのでそこまで労力はかからないはず!

タイトル通りJavaScriptだけで作るパターンと(ほぼ)no codeで作るパターンの2種類ありますが、JavaScriptだけで作る場合は基本的に無料で、no codeの場合は有料です(料金はあとで説明します)。

no codeは一応おまけなので最後にチラッと説明しますが、おすすめはこちらです。(有料ですがほぼno codeでしかも爆速)

選定技術

以下の技術を用いて作ってみます。
- 言語:JavaScript
- ツール:BigQuery、Cloud Functions

ただし、BigqueryやCloud Functionsはたくさん使うと有料になってしまうので気をつけてください。

Cloud FunctinosでBigQueryからデータを取得

ファイルの準備

Cloud Functionsを使ってBigQueryにあるデータにアクセスできるようにします。

まずはCloud Functionsを使える状態にします。

適当なディレクトリでCloud Functionsを導入します。
導入方法は調べると色々出てくると思うので割愛させて頂きます。

そうするとこんな感じのディレクトリ構成になると思います

your_dir
|-index.js
|-package.json
|-package-lock.json
|-node_modules

ここにBigqueryに接続するための接続キーが必要なので、こちらを参考に作成してください。(jsonで)

そのjsonファイルをyour_dir内に設置してください。
このjsonファイルの名前を仮にkey.jsonとしておきます。

関数の作成

bigqueryのライブラリを使用したいので次のコマンドを実行します。

npm install --save @google-cloud/bigquery

次にindex.jsを開いて次のように編集します。

index.js
const { BigQuery } = require("@google-cloud/bigquery");

const bigquery = new BigQuery({
  projectId: "作成したプロジェクトのID",
  keyFilename: "key.json", //ここでキーの場所を指定する
});

// BigQueryに問い合わせるクエリを記述
// ここでは7月の東京駅の気温を取得している
const query = "SELECT da, temp  FROM `bigquery-public-data.noaa_gsod.gsod2020` WHERE stn = "476620" and year = "2020" and mo = "07" ORDER BY da";

exports.bigquery = (req, res) => {
  //ヘッダーにこれらを入れないと後々作成するJavaScriptから呼び出せない
  res.header("Access-Control-Allow-Origin", "*");
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  bigquery
    .query(query)
    .then((data) => {
      const rows = data[0];
      res.send(rows);
    })
    .catch((error) => {
      console.log(error);
    });
};

ここでBigQueryが公開してるパブリックデータから東京駅の気温(華氏)を取得しています。

このパブリックデータは定期的に更新されるのでアクセスの度に最新版が取得されます。

※このクエリでBigQuery上で数十MB処理されます。アクセスする度にそれだけ処理されてたくさんアクセスすると無料枠をはみ出る恐れがあるので注意してください。

必要があれば自分で作ったテーブルにアクセスすることで処理量を減らしたり、定期的に行われるバッチ処理を作成してDBにデータを入れる等して処理量が無料枠に収まるようにしてください。

ここでは割愛しますが、BigQueryには定期的にテーブルを更新する(スケジュール)機能があるのでそちらを使うと処理量は減ると思います。

Cloud Functionsデプロイ

作った関数をデプロイします。
デプロイの方法は色々あるのですが、GCPのストレージに保存する方法を用います。

作った関数をStorageにアップロードしますのですが、まずアップロードするファイルを作成します。

作ったファイル類をzipファイルに圧縮します。

ここで気をつけたいのが、圧縮するのはファイル類です。
なので、以下のフォルダではなく、

Screen Shot 2020-07-11 at 20.56.28.png

こっちのファイル類をまとめて圧縮してください。(実際はjsonのキーファイルも一緒にまとめて圧縮)

Screen Shot 2020-07-11 at 20.56.39.png

そうしてできたzipファイルをストレージに保存します。

GCPの左のタブからStorage=>ブラウザを選びます。

Screen Shot 2020-07-11 at 20.59.28.png

その中でバケットを作成し、先ほど作ったzipファイルをアップロードします。

アップロードが完了したら、ストレージと同様にGCPの左のタブからCloud Functionsを選びます。

名前をテキトーにつけて、トリガーはHTTPにします。

Screen Shot 2020-07-11 at 21.02.22.png

デプロイ元は先ほど保存したzipファイルを指定します。
Screen Shot 2020-07-11 at 21.03.46.png

ランタイムはNode.js 10、実行する関数は先ほど作成したindex.jsで作ったbigqueryという関数です。

これでデプロイ完了です。

デプロイした関数を実行するURLにアクセスしてください。こんな感じで無事データが返ってきてるかと思います。

[{"da":"01","temp":75.5},{"da":"02","temp":79.5},{"da":"03","temp":73.1},{"da":"04","temp":77},{"da":"05","temp":76.4},{"da":"06","temp":78.3},{"da":"07","temp":81.1},{"da":"08","temp":78.9}]

日にちとその日にちでの気温(華氏)ですね。

ダッシュボードの作成

次は取得したデータを使ってグラフを作成します。
グラフはchart.jsを使います。

先ほどアクセスしたURLで取得できるjsonデータをchart.jsで読み込める形式に変換します。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>グラフ</title>
  </head>
  <body>
    <h1>棒グラフ</h1>
    <canvas id="myBarChart"></canvas>
    <script
      src="https://code.jquery.com/jquery-3.5.1.js"
      integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
      crossorigin="anonymous"
    ></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
    <script src="./main.js"></script>
  </body>
</html>

main.js
(function () {
  window.onload = function () {
    const url = "先ほど作成したcloud functionsのURL";

    const ctx = document.getElementById("myBarChart").getContext("2d");

    $.getJSON(url, function () {
      console.log("success");
    })
      .done(function (data) {
        let day = [];
        let temp = [];
        data.forEach((element) => {
          day.push(Number(element["da"]));
          // 摂氏に変換
          temp.push(((Number(element["temp"]) - 32) * 5) / 9);
        });

        const chart = new Chart(ctx, {
          type: "bar",
          data: {
            labels: day,
            datasets: [
              {
                label: "東京駅気温",
                data: temp,
                backgroundColor: "rgba(219,39,91,0.5)",
              },
            ],
          },
          options: {
            scales: {
              xAxes: [
                {
                  ticks: {
                    autoSkip: false,
                  },
                },
              ],
              yAxes: [
                {
                  ticks: {
                    suggestedMax: 30,
                    suggestedMin: 0,
                    stepSize: 1,
                    // 縦軸に単位を追加
                    callback: function (value, index, values) {
                      return value + "";
                    },
                  },
                },
              ],
            },
          },
        });
      })
      .fail(function () {
        console.log("error");
      })
      .always(function () {
        console.log("complete");
      });

  };
})();

これで下のようなグラフが得られると思います。

Screen Shot 2020-07-11 at 22.41.40.png

ダッシュボード自体はboot strap等のテンプレートを使えば簡単にそれっぽくできます。
今回はこちらを使いました。

Screen Shot 2020-07-12 at 8.38.28.png

おまけ

ここからはほぼno codeで作るダッシュボードです(コードはSQLのみ)。
有名BIツールであるTableauとGoogle Sheetを使用すると爆速でダッシュボードができます。

Tableau有料版を使うともっと簡単なのですが、なにせ非常に高額(年間10万円くらい)なので、Tableau Publicを使用します。

Google Sheetと連携させてデータを取得するのですが、Google SheetとBigQueryの連携にG Suiteの契約が必要になってきます(Enterprise版もしくはEssentials)。Essentialsの方は月10ドルなので割とお手頃かなと思います。今回はEnterprise版で行ったのでEssentialsの方での動作確認はしてません(ドキュメントに書いてあるからおそらく大丈夫だとは思いますが)。

BigQueryとGoogle Sheetsの連携

こちらの記事に全てが載っています。ありがたい。

これで定期的にGoogle Sheetsの内容を更新できます。

先ほどと同じようなクエリを作って、データを取得します。

Screen Shot 2020-07-12 at 16.53.38.png

このクエリを投げると60MBくらい処理するということです。
1TB/月までBigQueryは無料(少なくとも今は)なので、1日1回くらいなら全く問題ないですね。

結果は下記のようになります。同じクエリを投げてますが、クエリを投げた時間が違うのでda=09のレコードが増えてます。

Screen Shot 2020-07-12 at 16.54.13.png

TableauとGoogle Sheetsの連携

Google Sheetsで作成したデータをTableau Publicと連携させます。最初に開いた画面の左側のタブのTo a ServerのGoogle Sheetsを選択します。

Screen Shot 2020-07-12 at 17.36.23.png

接続確認を終了させると、Google Sheetsからデータを取得します。
そのデータから簡単に下のような図が作れます。

Screen Shot 2020-07-12 at 17.22.36.png

※Tableau Public、つまり無料版だとGoogle Sheetsのデータの反映は1日1回らしいので、頻繁に更新されるデータを即時反映させる場合にはTableauの有料版を使用する必要があります。

Tableauの使い方自体はここでは説明しませんが、色々なグラフを追加することで簡単にダッシュボードが作成可能です。

こちらで色々な格好良いダッシュボードが見られます。

ここで表示されてるのは本当に手間がかかっていてすごいんですが、例えばこのリンクの中の検索ボックスで「売り上げ分析」と検索して出てくるようなダッシュボードは割と簡単に作れます(もちろんものによりますが)。

ただ、Tableau Publicはデータが公開されてしまうので、外部に公開しても良いデータでないと使えないのが難点ですね。

まとめ

一応言語は一つ縛りだったので、JavaScriptしか使いませんでした。
(Node.jsもjsファイルだし、クエリもjsファイル内で書いてるし、htmlはプログラミング言語じゃないし、書こうと思えば全部JSで書けるから、これはもうJSしか使ってないと言っても過言ではないはず!)

お金があればno codeの方でやっちゃうのもありかな。

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

Vue.jsでdt、ddタグをv-forループで作る

dlタグをv-forループで生成するのは簡単ですよね

<template>
  <dl v-for="item in items">
    <dt>{{ item.name }}</dt>
    <dd>{{ item.description }}</dt>
  </dl>
</template><dl>
  <dt>えるしつているか</dt>
  <dd>りんごしかたべない</dt>
</dl>
<dl>
  <dt>死神は</dt>
  <dd>手が赤い</dt>
</dl>

でも一つのdlタグの中でdtとddを複数個作りたい時ありますよね。
むしろそっちが大多数かなと。

こうします。

<template>
  <dl>
    <template v-for="item in items">
      <dt>{{ item.name }}</dt>
      <dd>{{ item.description }}</dt>
    </template>
  </dl>
</template><dl>
  <dt>えるしつているか</dt>
  <dd>りんごしかたべない</dt>
  <dt>死神は</dt>
  <dd>手が赤い</dt>
</dl>

templateタグの中でtemplateタグを使えることを知りませんでした。

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

JavaScript(日付)

JavaScript日付取得一覧

js
var dt = new Date();

//
var year = dt.getFullYear();

//
var month = dt.getMonth()+1;

//
var date = dt.getDate();

//曜日
dateT = ["日","月","火","水","木","金","土"];
var day = dateT[dt.getDay()];

//
var hours = dt.getHours();

//
var minutes = dt.getMinutes();

//
var seconds = dt.getSeconds();

参考資料: https://www.sejuku.net/blog/22867

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

[Javascript] API Gateway に対して fetch メソッドでリクエストする

概要

「javascript から API Gateway にリクエストできるかどうか、とりあえず試したい」
そんなときは、ブラウザのコンソールを使うとラクチン(^ワ^*)

...というわけで、ブラウザに javascript の fetch メソッドを打ち込んでリクエストすることにするぞ!

今回はその方法のメモ

環境

API Gateway は API Key でアクセス制御を行っているものとする。

参考

ソース

ブラウザコンソールに打ち込むソース

fetch('API GatewayのURL', {headers: {"x-api-key": "API key をここに打つ"}}).then(response => response.json());

// sample
fetch('https://hogehoge12345.execute-api.us-east-2.amazonaws.com/xxxxxxx/sample', {headers: {"x-api-key": "abcdefghijklmnopqrstuvwxyz"}}).then(response => response.json());

// もしアクセス制御を一切行っていないのであれば、'headers': {} なしでよい
fetch('https://hogehoge12345.execute-api.us-east-2.amazonaws.com/xxxxxxx/sample').then(response => response.json());

参考

リクエスト結果の確認

  • ここをクリックすると確認できる
    image.png
  • 中身
    image.png

error対処

コンソールにこんなエラーが出ることがある

image.png

# エラー文
Access to fetch at 'https://url' from origin 'https://hoge' has been blocked by CORS policy:
  No 'Access-Control-Allow-Origin' header is present on the requested resource.

If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

これは API Gateway の "CORS有効化" という作業を行えば回避できる
※ただし、URLやAPI Keyが間違っているときもなぜかこのエラーが出た...なぜだ

参考

おわりに

ネットの海の情報だけでなんとかAPI Gatewayへのリクエストが行えました。
今度はReactで問い合わせるぞ( *˙︶˙*)وグッ!

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

SVGを書くのが面倒なので入力補完できるようにしてみる 2

前回

  1. SVGを書くのが面倒なので入力補完できるようにしてみる 1
    内部に持った状態を変更させるオブジェクト、メソッドチェイン、基本シェイプ、<path>のd要素
  2. SVGを書くのが面倒なので入力補完できるようにしてみる 2

引き続き ↓ コレ作ってるときの話です。
GitHub: mafumafuultu/svg.js dev

useでコピーして使いたい

複雑なシェイプを何度も何度も書くのは愚策、コピペで済むけども修正が面倒なので<use> <symbol> <g> <defs> を作っていく

image.png

こう書いて
svg(200, 200).$(
    defs().$(
        group().attrs({id: "foo"}).$(
            rect(0, 0, 10, 10)
        )
    ),
    symbol().attrs({id: "bar", width: 20, height: 20, viewBox: "0 0 20 20"}).$(
        circle(10, 10, 10)
    ),
    use("#foo").attrs({x: 50, y: 0, style: "fill:red;"}),
    use("#foo").attrs({x: 100, y: 50, style: "fill:green;"}),
    use("#foo").attrs({x: 150, y: 100, style: "fill:pink;"}),
    use("#bar").attrs({x: 0, y: 0, fill: "red"}),
    use("#bar").attrs({x: 50, y: 50, fill: "green"}),
    use("#bar").attrs({x: 100, y: 100, fill: "pink"}),
);
こんな出力がほしい
<svg width="200" height="200" viewBox="0 0 200 200">
    <defs>
        <g id="foo">
            <rect x="0" y="0" width="10" height="10" />
        </g>
    </defs>
    <symbol id="bar" width="20" height="20" viewBox="0 0 20 20">
        <circle cx="10" cy="10" r="10" />
    </symbol>
    <use xlink:href="#foo" x="50" y="0" style="fill:red;" />
    <use xlink:href="#foo" x="100" y="50"" style="fill:green;" />
    <use xlink:href="#foo" x="150" y="100" style="fill:pink;" />
    <use xlink:href="#bar" x="0" y="0" fill="red" />
    <use xlink:href="#bar" x="50" y="50"" fill="green" />
    <use xlink:href="#bar" x="100" y="100" fill="pink" />
</svg>

定義の追加

まず定義を追加しないと始まらない

const defs = () => wrapper(_svgTag('defs'));
const symbol = () => wrapper(_svgTag('symbol'));
const group = () => wrapper(_svgTag('g'));
const use = (id, attr={}) => wrapper(_svgTag('use')).attrs({href: id, ...attr});

xlink:href はSVG2で非推奨になるので、xlinkを外したものだけ書いておく
xlink:href MDN

要素.append の罠

svg(200, 200).$(...child)で要素を追加するとき

const __BASE_PROTO__ = {
    '@parse' : {
        /* 要素にappendできるように、子要素をunwrap */
        value (arr) {return arr.filter(v => null != v).map(v => '@' in v ? v['@'] : v)}
    },
    $: {
        value(...child) {
            /* 状態として持った要素に子要素を追加 */
            return this['@parse'](child).reduce((el, ch) => (el['@'].append(ch), el), this);
        }
    },
    /* 略 */
}

こんな感じでappendしていたら<use>は動かなかった。

要素.appenduse動かなかった原因

<use>がDOMに追加されるときに、参照したい要素がDOMに入っていないことが原因だと思われる。

解決方法

要素.innerHTMLにつっこむ。参照したい要素も同時にDOMに入るようになるので無事解決。

const __BASE_PROTO__ = {
    '@parse' : {
        /* 要素にappendできるように、子要素をunwrap */
        value (arr) {return arr.filter(v => null != v).map(v => '@' in v ? v['@'] : v)}
    },
    $: {
        value(...child) {
            /* 状態として持った要素に子要素を追加 */
            return this['@'].innerHTML += this['@parse'](child).reduce((t, v) => 'outerHTML' in v ? t + v.outerHTML : t + v, ''), this;
        }
    },
    /* 略 */
}

だいたい覚えていないanimation

「あっちの要素のアニメーション終了時にこっちのアニメーションを開始して、500msでこの座標、そこからたっぷり3000msかけてこのパスに沿って1周200msで時計回りに回転しながら移動、それ3回繰り返してから、こっちのアニメーションを...」
はい、タグ打つのを辞めたくなる感じの要求ですね。
こんなものだって、入力補完があれば何とかなるんじゃないかなと考えて作ってる最中です。

const animate = (id, ms = 1000, repeat='indefinite', target) => ({
    op : {id: id, dur: `${ms}ms`, repeatCount: repeat, attributeName: target, attributeType:"XML",},
    timeFunc: [],
    // キーフレームの指定
    value (...val) {return delete this.op.keyTimes, this.op.values = val.join(';'), this;},
    timeLine (timetable = {'0' : 0, '1': 1}) {return Object.assign(this.op, {values: Object.values(timetable).join(';'), keyTimes : Object.keys(timetable).join(';')}), this;},
    rotate (r = '0') {return this.op.rotate = r, this;},
    spline(...splines) {return Object.assign(this.op, {calcMode : 'spline', keySplines: splines.join(';')}), this;},
    /* 略 */
    close() {return vg(_svgTag('animate')).attrs(this.op).$(this.timeFunc.map((tag, opt) => wrapper(_svgTag(tag)).attrs(opt)))}
});

<animatePath>とか<animateTransform>も同様に属性を状態として持たせて、状態を変化させる関数をチェインする仕組みにしています。

以下の仕様例は<animateTransform>ですが、こんな具合になればいいなと

line(0, 0, 0,-200).attrs({stroke: 'black'}).$(
    animateTransform('time_sec', 60000, none,'transform').rotate().values(0, [0, 0], 360, [0, 0]).close()
)
<line x1="0" y1="0" x2="0" y2="-200" stroke="black">
    <!-- 線の片側の(0, 0)を原点にして1分かけて360°回転 = 秒針 -->
    <animateTransform id="time_sec" dur="60000ms" repeatCount="indefinite" attributeName="transform" attributeType="XML" type="rotate" from="0 0,0" to="360 0,0"></animateTransform>
</line>

今回は、これら<use> <animate>周りでおなか一杯になる感じですね。
3次元的なアニメーションをするときは4x4の行列の計算も出てくるので、そのあたりをどう実装してやろうか考えている最中です。

(@╹ω╹@) < ぎょーれつのしょりはたぶんできていたきがする

ここまでやって75%完成というところでしょうか?では、また次回

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

jQueryでtextareaの高さを自動調節する

テキストエリアの高さを行数に応じて自動調節する方法を調べた結果、最終的に超シンプルな方法に落ち着いたのでメモ。検索で引っかかるどれよりもどシンプルに仕上がっていると思います。

使っているもの

名前 ver
macOS Catalina 10.15.5
jQuery.min.js v3.4.1(googleapisからリンクで取得)

使う言語

  • javascript(というかjQuery)
  • html

まずは結論

htmlファイルのbody上部にこれさえ突っ込めばOKです。
必要に応じて呼び出し元の要素の指定をidやクラスに変更してください。

※v1.7以上のjQueryを別途読み込んでいる場合、一行目(src=とか書いてあるscriptタグ)は不要です。
※v1.7未満のjQueryを別途読み込んでいる場合、$(documend).onが使えません。
https://api.jquery.com/on/
c1.7未満のjQueryを使う場合は代わりにbindが使えそうですが、試していないので保証はできません。
https://www.aipacommander.com/entry/2015/09/17/192659

表示したいページのhtml.html
--前略 ヘッダとか諸々--
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
    $(document).on("change", "textarea", function(evt){
        var min_height = 40; //テキストエリアの最小の高さをお好みで設定
        $(evt.target).height(min_height); //一旦最小サイズにする
        $(evt.target).height(evt.target.scrollHeight); //スクロールなしでテキストが収まる最小の高さに上書き
    });
</script>

--後略 この下にテキストボックスなど--
html中で読み込んでおくCSS.css
textarea{
    resize: none;/*ユーザによるリサイズを不可にし、右下に///が出ないようにする*/
}

コード中の"textarea"部分がセレクタの指定箇所なので、"#id名"なり".class名"なり"textarea[name='テキストエリア名']"なり状況に応じたセレクタに変更しましょう。

答えにたどり着くまで

まずは「textarea 自動リサイズ」とか「textarea 高さ 調節」なんかで検索して調べました。

  1. 改行コードの数を取得する系
    改行コードを正規表現で抽出&その数に応じて幅を変えるというアプローチ。
    例: https://qiita.com/ampersand/items/ceaa5066d44990d30df3
    ただ、自動改行も取得できないと困るので目的に合わず。

  2. 親要素のサイズを自動変更し、それに追随させる
    例:https://qiita.com/tsmd/items/fce7bf1f65f03239eef0
    ちょっと複雑で設定ミスが怖いので見送り。

  3. textareaが持つscrollHeightの値を使う

    1. https://qiita.com/osamingo/items/3ee00333f6fcd33fa2a1 scrollHeightを使い、テキストエリアから文字がはみ出していたら最適なサイズまで拡大。ただ、文字を減らしたときにリサイズしない。
    2. https://pisuke-code.com/jquery-make-textarea-auto-resize/ while文を使ってちょっとずつ高さを減らし、スクロールが必要になったら表示可能な最小サイズにして終了、というアプローチ。

3-2が最も目的にあっていたのですが、whileを使っているのでミスって無限ループを発生させ、PCが唸りを上げること数回。
修正が怖くなったので、whileを使わずになんとかしようと思った結果、きづきました。
一度最小まで縮めて、はみ出てたら拡大すればいい。それだけのことでした。

理屈

  1. jQueryを使いたいので、googleapisからjQueryを読み込みます。
  2. テキストエリアの値が変更されたときに、jQueryの機能を使って検出します。
  3. 値が変更されたテキストエリアのサイズを、一旦min_heightに変更します。
  4. scrollHeightの値で高さを上書きします。

ちなみに、テキストエリアの幅が余っているときに3の機能だけで調整できないのは、以下が原因です。

要素のコンテンツが垂直スクロールバーを表示することなく収まる場合、その scrollHeight は clientHeight と等しくなります。
https://developer.mozilla.org/ja/docs/Web/API/Element/scrollHeight

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

(解答例) JavaScript 暗黒問題集

これは解答例です。

問題はこちら ⇒ JavaScript 暗黒問題集 - Qiita

ここ見に来る人はたぶん仕様書くらい読めるだろうとの想定の元、一々当該箇所を参照しません(手抜き)。

解答と解説

問1. 私は私ではない

以下の関数 q1true を返すような値 x を与えよ。

function q1(x) {
  return x !== x;
}

解答例

q1(NaN);

解説

厳密等価/不等価演算子(===, !==)において、NaN は等価であるとみなされない。

単に知識を問う問題。あるいは、問 2 以降も解けそうだなと思ってもらうためのトラップ。

問2. 直観に反する

以下の関数 q2true を返すような値の組 a, b を与えよ。

function q2(a, b) {
  return a == b && a != b;
}

解答例

q2(0, {
  value: 0,
  valueOf() {
    return this.value++;
  }
});

解説

厳密でない等価/不等価演算子(==, !=) を用いているので、型変換が行われる。

オブジェクト型がプリミティブ型に変換される際には、valueOf メソッドが呼び出されるので、そこで副作用を起こしてしまえば良い。

問3. さよなら推移律

以下の関数 q3true を返すような値の組 a, b, c を与えよ。

function q3(a, b, c) {
  return a == b && b == c && a != c;
}

解答例

q3("-0", 0, "+0");

解説

問 2 と同様に valueOf メソッドを用いて副作用を起こしても良いが、型変換の優先順位に着目すれば、より簡単に解くことができる。

解答例では、a == b"-0" == 0 は、文字列から数値への変換が行われ 0 == 0 となる。

b == c0 == "+0" も同様に 0 == 0 となる。

a != c"-0" != "+0" は、どちらも文字列であるため、型変換が行われず文字列として比較される。

問4. 同じトコロに違うモノ

以下の関数 q4true を返すような値 x を与えよ。

function q4(x) {
  const a = [0, 1];
  return x == x + x && a[x] != a[x + x];
}

解答例

q4({
  valueOf() {
    return 0;
  },
  toString() {
    return "1";
  }
});

q4(false);

解説

問 2 で述べたように、プリミティブ型が要求される演算でオブジェクト型を使用する場合には valueOf メソッドが呼び出される。valueOf メソッドがプリミティブ型を返さなかった場合は、toString メソッドが呼び出されて文字列に変換される。

一方で、プロパティキーとしてオブジェクト型が使用されたときには、valueOf メソッドを 呼び出さずに 直接 toString メソッドが呼び出される。

解答例の前者では、以下のように変換が行われている。

  • x == x + x0 == 0 + 00 == 0
  • a[x]a["1"]
  • a[x + x]a[0 + 0]a[0]

解答例の後者では Boolean の加算が数値に変換されることを利用して、false を与えている。

  • x == x + xfalse == false + false0 == 0 + 00 == 0
  • a[x]a["false"] (プロパティが存在しないので undefined となる)
  • a[x + x]a[0 + 0]a[0]

問5. 実例の実例

以下の関数 q5true を返すような値 x を与えよ。

function q5(x) {
  return x instanceof x;
}

解答例

q5(Function);

q5(Object);

function v1() {}
v1.prototype = Function.prototype;
q5(v1);

function v2() {}
Object.setPrototypeOf(v2, v2.prototype);
q5(v2);

解説

A instanceof B は、クラスベースのオブジェクト指向言語においては、A がクラス/インターフェース B のインスタンスであるかを判定するものであることが多い。

一方で、JavaScript は class 記法が導入されたものの実態はプロトタイプベースである。

JavaScript における instanceof 演算子は「A のプロトタイプチェーン中に B.prototype が存在するか」を判定するものである。

instanceofの挙動
// A.__proto__.__proto__ ... __proto__ === B.prototype;

let p = A.__proto__;
while (p) {
  if (p === B.prototype) {
    return true;
  }
  p = p.__proto__;
}
return false;

問題では上記条件を満たしたうえで A == B となるものを与えれば良い。
(※仕様上、B が関数でなければ実行時エラーとなる点には注意。)

Function コンストラクタは、この条件を満たしている。

Function.__proto__ === Function.prototype;

また Object コンストラクタも条件を満たす。

Object.__proto__.__proto__ === Object.prototype;

もちろん、解答例 v1, v2 のように、自力で目的のプロトタイプチェーンを構築しても良い。

問6. 入ってます

以下の関数 q6true を返すような値 x を与えよ。

function q6(x) {
  return x in x;
}

解答例

q6({
  toString() {
    return "toString";
  }
});

q6(new String("length"));

q6([0]);

解説

in 演算子は、A in B において、列挙可能なプロパティ名 A が B に存在することを判定する。

A は文字列またはシンボルであることが期待されるが、いずれでもなければ toString メソッドにより文字列に変換される。

B はプリミティブ型の場合実行時エラーとなるので、オブジェクト型である必要がある。

というわけで、「自身の列挙可能なプロパティ名に合致する文字列に変換可能なオブジェクト」を渡せばよい。

解答例のように、直接オブジェクトを作成しても良いし、文字列のラッパーオブジェクト(String)を利用する手もある。

変わり種としては配列 [0] も文字列 "0" に変換され、[0]["0"] が存在するので条件を満たすことができる。

問7. いちたりない

以下の関数 q7true を返すような値 x を与えよ。

function q7(x) {
  return Array.isArray(x) &&
    !Array.prototype.some.call(x, v => v) &&
    x.length === 7 &&
    new Set(x).size === 7 &&
    Array.prototype.reduce.call(x, i => i + 1, 0) === 6;
}

解答例

q7([, 0, 0n, null, false, NaN, ""]);

解説

複数の条件を満たすオブジェクトを与える問題である。

それぞれの条件を見ていくと、

  • 条件 1. Array.isArray(x)
    • x は配列である
  • 条件 2. !Array.prototype.some.call(x, v => v)
    • 要素はいずれも真と評価されない(すべて偽と評価される)
  • 条件 3. x.length === 7
    • 長さは 7 である
  • 条件 4. new Set(x).size === 7
    • 集合に変換しても大きさが 7 である
    • つまり、要素に重複が無い
  • 条件 5. Array.prototype.reduce.call(x, i => i + 1, 0) === 6
    • reduce を用いてカウントした場合には 6 になる。

条件 1 から 4 までで、異なる 7 種の偽と評価される値を配列で渡せ、と言っていることになる。

JavaScript (ES2020) において、(環境依存のものを除けば) 偽と評価されるのは以下の 7 種である。

  • false
  • NaN
  • "" (空文字列, ''`` は同じもの)
  • null
  • undefined
  • 0
    • +0, -0 を区別することもできるが今回は Set を用いているので同一
  • 0n
    • BigInt の 0 (ES2020 で導入)
    • +0n, -0n を区別することもできるが以下略

これに加えて条件 5 では、reduce を用いると総数が 6 になる、と言っている。

reduce 始め、mapfilter 等のメソッドは、疎な配列の場合空要素を無視するという特性があるので undefined を空要素に置き換えてやればよい。

「配列と集合の基礎」+ 「Falsyな値」+「新たに加わった BigInt」+「疎な配列の扱い」をすべて抑えていれば解ける、という問題。

問8. おおきすぎる

以下の関数 q8 が、必ず true を返すような関数 f を与えよ。

function q8(f) {
  const n = 4294967297;
  const i = Math.floor(Math.random() * n);
  const x = f(n);
  return x[i] === i;
}

解答例

q8(n => {
  return new Proxy({}, {
    get(target, property, receiver) {
      return Number(property);
    }
  });
});

解説

一見、インデックスと同じ値を入れた配列を作れば良いようにも思えるが、JavaScript の配列は長さが $2^{32} - 1$ まで、という制限があるため作成することができない。4294967297 は $2^{32} + 1$ である。そもそも実際に作成するには、おおきすぎる。

解答例では配列ではなく Proxy を用いて、アクセスされたプロパティ名を数値に変換して返すオブジェクトを作成している。

別解と補遺

別解として、メモリを無視して仕様のみを考えれば以下のような解答もあり得る。

// 別解1
q8(n => {
  const a = new Float64Array(n);
  a.forEach((_, i, a) => a[i] = i);
  return a;
});

// 別解2
q8(n => {
  const o = {};
  for (let i = 0; i < n; i++) {
    o[i] = i;
  }
  return o;
});

別解 1 では、型付き配列を使用している。
型付き配列は通常の配列と違い、長さの制限が $2^{53}-1$ (double で正しく表現できる最大の整数)である。

別解 2 では、配列でなく通常のオブジェクトを用いている。

現行の仕様ではこれらは正しいコードであるはずなので、一応別解として載せた。

ただし現実問題としてメモリが潤沢にあったとしてもこれらを実行可能な処理系は、私は見たことが無いし、おそらく現状存在しない。

と言うのは、JavaScript の他の仕様が $2^{32}$ 個以上の要素/プロパティを持つオブジェクトを想定していないためである。例として Object.keys がある。これは、オブジェクトのプロパティ名の一覧を「配列で」取得するメソッドであり、最大長 $2^{32}-1$ まで、という制限を受ける。

このような状況であるため、ほとんどの処理系においては要素数・プロパティ数が$2^{32}-1$を超えないという前提が置かれており、別解のコードは仕様に無い実行時例外を引き起こすかクラッシュする。型付き配列の上限が $2^{53}-1$ と言うのも絵にかいた餅である。

と、言うことを伝えたいがために、この問題を加えた。
変な別解が存在するので悪問の類だと思う。すまぬ。

問9. 見慣れない構文

以下の関数 q9 が、必ず true を返すような値 each を与えよ。

function q9(each) {
  const array = new Array(10);
  for (let i = 0; i < 10; i++) {
    array[i] = Math.floor(Math.random() * 10);
  }

  const total = array.reduce((x, y) => x + y, 0);

  for (each.item of array);

  return each.sum === total; 
}

解答例

q9({
  sum: 0,
  set item(v) {
    this.sum += v;
  }
});

解説

一般に for-of は、以下のような形式で用いることが多い。

for (let x of elements) {
  // ...
}

of の左辺での変数宣言は必須ではなく、単に変数を使ってもかまわない。

let x;
for (x of elements) {
  // ...
}

というか、代入の左辺になれる式なら、変数でなくて良い。

let a = [0];
for (a[0] of elements) {
  // ...
}

そして、of の左辺式はループの回数だけ評価される。

let count = 0;
function f() {
  count++;
  return { dummy: 0 };
}

for (f().dummy of elements) {
}

console.log(count === elements.length); // true

つまり、以下はだいたい同じだと思えば良い。

// これは
for ( of elements) {
}

// これとだいたい同じこと
for (let item of elements) {
   = item;
}

と言うわけで、setter を仕掛けてしまえば問のようなコードが実現できる。

問10. 次の日は別の人

以下の非同期関数 q10 が、最終的に true を返すような関数 f を与えよ。

async function q10(f) {
  const x = await f();
  const y = await x;
  return !Object.is(x, y);
}

// q10(answer).then(value => console.log(value)); // true

解答例

q10(() => {
  const obj1 = {};
  Promise.resolve().then(() => {
    obj1.then = resolve => {
      const obj2 = {};
      resolve(obj2);
    };
  });
  return obj1;
});

解説

まず前提知識として、awaitPromise の結果を待つもの......というわけではなく、then と言う名前のメソッドで一回限りのコールバックを設定できるオブジェクト(Thenable) であれば、なんでも待つことができる。

await new Promise(callback => {
  setTimeout(() => callback("some result value"), 1000);
});

// 以下でも同様
const promiseLike = {
  then(callback) {
    setTimeout(() => callback("some result value"), 1000);
  }
};
await promiseLike;

また、await は、与えられた値が Thenable でなければ、値をそのまま返す。(処理自体は一度中断される)

const v = await 10;
console.log(v); // 10

加えて、Thenable で返された値が Thenable であった場合、Thenable でなくなるまでさらに待つ。

const nested = {
  then(callback1) {
    callback1({
      then(callback2) {
        callback2(10);
      }
    });
  }
};

const result = await nested;
console.log(result); // 数値の 10, オブジェクトではない

上記特性を踏まえれば、Thenable の Thenable (あるいは PromisePromise) を作る方針ではこの問題はうまく行かないことがわかる。

1 回目の await で最終結果まで待機してしまうので、2 回目の await では同じ結果になってしまう。等価演算子 === ではなく Object.is を用いているので、最終結果として NaN を返しても true にはならない。

解答例では、まず Thenable でないオブジェクトを返した上で、2 回目の await までにプロパティを追加して Thenable に変えることで異なる値になるようにしている。

実行順序としては、以下のようになる。

  • q10 の実行が開始する
    • f が実行される
      • obj1 を生成する
      • Promise.resolve().then(...) に渡したクロージャ (A) がジョブキューに積まれる
      • fobj1 を返す
    • obj1 を値として 1 回目の await
      • obj1 は Thenable でないので、await の結果は obj1 となる
      • q10 の残りの部分 (B) がジョブキューに積まれる
    • 終了
  • ジョブキューから (A) が取り出され実行される
    • obj1then メソッドを追加する (Thenable となる)
    • 終了
  • ジョブキューから (B) が取り出され実行される
    • await の結果 obj1x に代入される
    • x (=obj1) を値として 2 回目の await
      • この時点では、obj1 は Thenable なので obj1.then が呼び出される
        • コールバックにより obj2 が次の await の値となる
        • obj2 は Thenable ではないので、await の結果は obj2 となる
      • q10 の残りの部分 (C) がジョブキューに積まれる
    • 終了
  • ジョブキューから (C) が取り出され実行される
    • await の結果 obj2y に代入される
    • xy は異なるオブジェクトなので !Object.is(x, y)true となる
    • true を返却し、q10 の実行が終了する

所謂マルチスレッドと異なり、async/await は FIFO でスケジューリングされることが保証されているので、順序が入れ替わることは無い。

また、実行単位が仕様中で Job と表現されているのでスケジュールに用いる構造を便宜上「ジョブキュー」と表現したが、仕様書中では明確な名称は与えられていない。(単に enqueue するという扱い)。

おわりに

暗黒ってなんだろうな、と考えた結果、「べからず」集かなあと言うことでこんな感じになった。

問 2-4 は「普段は厳密等価演算子使おう」「想定困難な副作用を起こすな」と言う話であるし、
問 5-7 は「演算子や関数の意味を類推してなんとなくで理解するのやめよう」という話である。(問7は他の要素も入っているけれど)

問 8 は、べからずと言うよりは、無謀な実装しないで無難なワークアラウンド探そう、という話。(そして仕様と実装の狭間で起こる嘆きについて)

問 9-10 は、まあ、あまり遭遇しないと思うので、どちらかと言えば単に腕試しだろうか。解答例自体が「べからず」である、と言ってもいいかもしれない。


各種言語において色々な「べからず」はあると思うのだけれど、個人的に「べからず」に頼ったプログラミングというのが、あまりよくないなあと思っている。

「○○は危険なメソッドなのでこれからは△△を使いましょう!」と言っていた人が、結局どちらも理解しておらずバグを作りこむさまを過去に何度か見ている(し、自分にも心当たりがある。)

「避ける」よりも「知る」ほうが安全なのではないか、と言うのが今のところの持論であるのだけれど、実際に「やってはいけない」ことの対象ではなく機序を敢えて深く「知る」機会はあまりない。時間があったら「良い」ものを知ろうとするのが普通ですし。

じゃあ遊びに持っていけたらいいのかな、と言うことでこんなものを作ってみた次第。

何かが達成できているとは思っていないけれど、貴重なお暇を潰せたなら恐悦至極。

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

【Javascript】フレームワークを使わずにドラッグアンドドロップでjsonを読み込む

TL;DR

記事末のソースを見てね!

やりたいこと

Github Pagesで、静的ページだけどドラッグアンドドロップでjsonファイルを読み込みたかった。

ポイント

1. ブラウザデフォルトのドラッグアンドドロップ関係のイベント処理を止める

ブラウザにファイルをドラッグアンドドロップすると、ドロップしたファイルを読み込んでブラウザの機能としてプレビューする、またはダウンロードしてしまいます。まずはこれを止めます。

Javascript
const drop_area = document.getElementById("drop_area");

// リストのイベントすべてに同じ処理をセットする
["drag", "dragstart", "dragend", "dragover", "dragenter", "dragleave"].forEach(event => {
  drop_area.addEventListener(event, (e) => {
    // ブラウザデフォルトのイベント処理を停止
    e.preventDefault();
    e.stopPropagation();
  });
});

2. ドロップイベントがあったとき非同期通信でGETする

Github PagesはPOSTメソッドを受け付けていないので(ステータスコード405が帰ってくる)、GETメソッドで行きます。

Javascript
function handleDropAction(e) {
  // ブラウザデフォルトのイベント処理を停止
  e.preventDefault();
  e.stopPropagation();

  const droppedFiles = e.dataTransfer.files; // ドロップしたファイルを取得

  // ファイルが一つ以上ドロップされたら
  if (droppedFiles.length > 0) {
    const ajaxData = new FormData();
    const ajax = new XMLHttpRequest();
    const file = droppedFiles[0]; // 2つ目以降のファイルを今回は無視する

    ajaxData.append("file", file); // 非同期通信で送信するデータにドロップしたファイル情報を追記
    ajax.open("GET", "index.html");  // 非同期通信の準備。普通のサーバならPOSTでも可

    // 送信が終わったときの処理
    ajax.onload = (e) => {
      if (ajax.status >= 200 && ajax.status < 400) { // 送信に成功したら読み込む
        const fr = new FileReader();

        // ファイルの読み込みが終わったときの処理
        fr.onload = () => {
          document.getElementById("viewer").innerText = fr.result;
        };
        fr.readAsText(file); // ドロップしたファイルをテキストとして読み込み
      }
    }
    ajax.send(ajaxData); // ドロップしたファイルを表示中のページに送信する
  }
}

// あらかじめ定義しておく
// const drop_area = document.getElementById("drop_area");

// id="drop_area"要素にドロップしたとき呼び出す関数を関連付け
drop_area.addEventListener("drop", handleDropAction); 

注意

外部ファイルの読み込みになるので、file://で表されるURLではオリジン間リソース共有(CORS)扱いになってエラーが出て、うまく処理されません。

ローカルで試す場合は、PHPやPython、Rubyなどで簡易的にHTTPサーバを立てるとよいでしょう。

shell
# PHPでHTTPサーバを立てる例
cd /path/to/src/ # index.html のあるディレクトリに移動しておく
php -S localhost:8080  # http://localhost:8080/ にアクセスするとうまく動く

ソースコードまとめ

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sample</title>
</head>
<body>
  <div id="drop_area" style="width:100%; height:100%;">drop area</div>
  <div id="viewer"></div>
</body>
<script>
  function handleDropAction(e) {
    // ブラウザデフォルトのイベント処理を停止
    e.preventDefault();
    e.stopPropagation();

    const droppedFiles = e.dataTransfer.files; // the files that were dropped

    if (droppedFiles.length > 0) {
      const ajaxData = new FormData();
      const ajax = new XMLHttpRequest();
      const file = droppedFiles[0];

      ajaxData.append("file", file);
      ajax.open("GET", "index.html");  // POSTでも可
      ajax.onload = (e) => {
        if (ajax.status >= 200 && ajax.status < 400) {
          const fr = new FileReader();
          fr.onload = () => {
            document.getElementById("viewer").innerText = fr.result;
          };
          fr.readAsText(file);
        }
      }
      ajax.send(ajaxData);    
    }
  }

  const drop_area = document.getElementById("drop_area");
  ["drag", "dragstart", "dragend", "dragover", "dragenter", "dragleave"].forEach(event => {
    drop_area.addEventListener(event, (e) => {
      // ブラウザデフォルトのイベント処理を停止
      e.preventDefault();
      e.stopPropagation();
    });
  });
  drop_area.addEventListener("drop", handleDropAction);
</script>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 暗黒問題集

あらすじ

最近 JavaScript を学び始めた知り合いが、JavaScript は暗黒の言語であるという話をどこかで聞いてきたらしい。

この言語を愛して止まない私としては「そうだね」以外の言葉が出てこなかったので、開き直って暗黒面を育む問題集をひっそりと作り始めたのであった。

想定対象

「JavaScript、だいたいわかります」という諸氏に遊んでいただければ幸い。

初学者にとっては混乱を招く内容にしかならないので、避けたほうが良いように思う。

前提条件

  • ES2020 (Standard ECMA-262 11th edition) に従う。
  • strict モード ("use strict" 指定) で実行される。
  • 組み込みのオブジェクトを変更してはならない。
  • ブラウザの機能等、環境依存の API を用いてはならない。

問題

全 10 問

時間制限はない

解は複数存在し得る

問1. 私は私ではない

以下の関数 q1true を返すような値 x を与えよ。

function q1(x) {
  return x !== x;
}

問2. 直観に反する

以下の関数 q2true を返すような値の組 a, b を与えよ。

function q2(a, b) {
  return a == b && a != b;
}

問3. さよなら推移律

以下の関数 q3true を返すような値の組 a, b, c を与えよ。

function q3(a, b, c) {
  return a == b && b == c && a != c;
}

問4. 同じトコロに違うモノ

以下の関数 q4true を返すような値 x を与えよ。

function q4(x) {
  const a = [0, 1];
  return x == x + x && a[x] != a[x + x];
}

問5. 実例の実例

以下の関数 q5true を返すような値 x を与えよ。

function q5(x) {
  return x instanceof x;
}

問6. 入ってます

以下の関数 q6true を返すような値 x を与えよ。

function q6(x) {
  return x in x;
}

問7. いちたりない

以下の関数 q7true を返すような値 x を与えよ。

function q7(x) {
  return Array.isArray(x) &&
    !Array.prototype.some.call(x, v => v) &&
    x.length === 7 &&
    new Set(x).size === 7 &&
    Array.prototype.reduce.call(x, i => i + 1, 0) === 6;
}

問8. おおきすぎる

以下の関数 q8 が、必ず true を返すような関数 f を与えよ。

function q8(f) {
  const n = 4294967297;
  const i = Math.floor(Math.random() * n);
  const x = f(n);
  return x[i] === i;
}

問9. 見慣れない構文

以下の関数 q9 が、必ず true を返すような値 each を与えよ。

function q9(each) {
  const array = new Array(10);
  for (let i = 0; i < 10; i++) {
    array[i] = Math.floor(Math.random() * 10);
  }

  const total = array.reduce((x, y) => x + y, 0);

  for (each.item of array);

  return each.sum === total; 
}

問10. 次の日は別の人

以下の非同期関数 q10 が、最終的に true を返すような関数 f を与えよ。

async function q10(f) {
  const x = await f();
  const y = await x;
  return !Object.is(x, y);
}

// q10(answer).then(value => console.log(value)); // true

解答例

(解答例) JavaScript 暗黒問題集 - Qiita

ネタバレになりそうなコメントは、解答記事の方にしていただけると助かります。

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

【rails】ランダムのパスワードを作成しパスワードフィールドに入力する方法

この記事を参考にすればできること

-ランダムのパスワードを生成

-

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

【rails】ランダム英数字のパスワードを作成しパスワードフィールドに入力する方法

パスワードフィールドにパスワードを生成して入力させよう

ポートフォリオの投稿機能にパスワード機能を設定したのですが、
パスワードを自動的に生成、入力までしてくれる機能あったら便利じゃん。

ということで作りました。

以下画像のように「パスワードを生成する」ボタンをクリックすると、
自動的に入力できるようにしましょう!
パスワード生成.gif

パスワードを生成する

まずはコントローラーでランダムの英数字を作れるようにします。

topics_controller.rb
def new
  @topic    = Topic.new
  @password = SecureRandom.alphanumeric(6)
  -> ランダムの英数字(A-Z, a-z, 0-9)を生成
   (6)は6桁、指定なしの場合は16桁になります。
end

パスワードをパスワードフィールドに入力しよう

次に先ほど作したパスワードをpassword_field(今回はtext_fieldにしています)に
入力できるようにします。

new.html.erb
<div class="topic-new-wrapper" >
  <div class="container">
    <div class="row">
      <div class="col-md-6 col-md-offset-3">
          <%= form_for @topic do |f| %>
            <div class="form-group">
              <%= f.label :password, 'password(任意)' %>
              <%= f.text_field :password, class: 'form-control', id: 'password' %>
              <%= button_tag 'パスワードを生成する', id: 'auto-fill-link' %>
            </div>

            <%= f.submit 'パスワード登録', class: 'btn btn-black btn-block' %>

          <% end %>
      </div>
    </div>
  </div>
</div>

<script>
  $(function(){
    autoFill();
    function autoFill() {
      $('#auto-fill-link').click(function(){
        $('#password').val("<%= @password %>");
      });
    }
  })
</script>

auto-fill-linkというidを含むリンクがクリックされると、passwordというidを含む
fieldに@passwordが入力されます。

ただこれだとbutton_tagがクリックされたときにsubmitされてしまうため、
ただのボタンとして利用する際は以下のコードを追加してあげます。

type: "button"

new.html.erb
<div class="topic-new-wrapper" >
  <div class="container">
    <div class="row">
      <div class="col-md-6 col-md-offset-3">
          <%= form_for @topic do |f| %>
            <div class="form-group">
              <%= f.label :password, 'password(任意)' %>
              <%= f.text_field :password, class: 'form-control', id: 'password' %>
              <%= button_tag 'パスワードを生成する', id: 'auto-fill-link', type: "button" %>
            </div>

            <%= f.submit 'パスワード登録', class: 'btn btn-black btn-block' %>

          <% end %>
      </div>
    </div>
  </div>
</div>

<script>
  $(function(){
    autoFill();
    function autoFill() {
      $('#auto-fill-link').click(function(){
        $('#password').val("<%= @password %>");
      });
    }
  })
</script>

まとめ

パスワードの生成から入力までしてくれる機能を作成しました。

簡単なので上記のコードぜひ使ってくださいね。

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

【rails】ボタンをクリックするとランダムの英数字パスワードを作成、パスワードフィールドに入力する方法

パスワードを自動生成したい

ポートフォリオの投稿機能にパスワード機能を設定したのですが、
パスワードを自動的に生成、入力までしてくれる機能あったら便利じゃん。

ということで作っていきましょう!

この記事のゴール

「パスワードを生成する」ボタンをクリックして
自動的に入力できるようにしましょう!
パスワード生成.gif

前提

rails:5.2
ruby:2.6.3
bootstrap
jquery

パスワードを生成する

まずはコントローラーでランダムの英数字を作れるようにします。

topics_controller.rb
def new
  @topic    = Topic.new
  @password = SecureRandom.alphanumeric(6)
  -> ランダムの英数字(A-Z, a-z, 0-9)を生成
   (6)は6桁、指定なしの場合は16
end

ボタンクリックでフィールドに入力できるようにする

次に先ほど作したパスワードをpassword_field(今回はtext_fieldにしています)に
入力できるようにします。

new.html.erb
<div class="topic-new-wrapper" >
  <div class="container">
    <div class="row">
      <div class="col-md-6 col-md-offset-3">
          <%= form_for @topic do |f| %>
            <div class="form-group">
              <%= f.label :password, 'password(任意)' %>
              <%= f.text_field :password, class: 'form-control', id: 'password' %>
              <%= button_tag 'パスワードを生成する', id: 'auto-fill-link' %>
            </div>

            <%= f.submit 'パスワード登録', class: 'btn btn-black btn-block' %>

          <% end %>
      </div>
    </div>
  </div>
</div>

<script>
  $(function(){
    autoFill();
    function autoFill() {
      $('#auto-fill-link').click(function(){
        $('#password').val("<%= @password %>");
      });
    }
  })
</script>

"auto-fill-link"というidを含むリンクがクリックされると、"password"というidを含む
fieldに@passwordが入力されます。

ただこれだとbutton_tagがクリックされたときにsubmitされてしまうため、
ただのボタンとして利用する際は以下のコードを追加してあげます。

type: "button"

new.html.erb
<div class="topic-new-wrapper" >
  <div class="container">
    <div class="row">
      <div class="col-md-6 col-md-offset-3">
          <%= form_for @topic do |f| %>
            <div class="form-group">
              <%= f.label :password, 'password(任意)' %>
              <%= f.text_field :password, class: 'form-control', id: 'password' %>
この行->       <%= button_tag 'パスワードを生成する', id: 'auto-fill-link', type: "button" %>
            </div>

            <%= f.submit 'パスワード登録', class: 'btn btn-black btn-block' %>

          <% end %>
      </div>
    </div>
  </div>
</div>

<script>
  $(function(){
    autoFill();
    function autoFill() {
      $('#auto-fill-link').click(function(){
        $('#password').val("<%= @password %>");
      });
    }
  })
</script>

まとめ

パスワードの生成から入力までしてくれる機能を作成しました。

簡単なので上記のコードぜひ使ってくださいね。

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

for, for in, for of, forEach まとめ

for

  • 基本的な書き方
const colors = ["red", "yellow", "green"]

for (let i = 0; i < colors.length; i++) {
  console.log(colors[i])
}

for of

  • ES6
  • for文をより簡潔に記述できる
  • 配列要素の繰り返し処理
const colors = ["red", "yellow", "green"]

for(color of colors){
    console.log(color)
}

for in

  • ES6
  • ブジェクトのプロパティ(キー)分ループしてくれる
const colors = { one: "red", two: "yellow", three: "green" }

for (key in colors) {
  console.log(`${key} : ${colors[key]}`)
}

forEach

  • ES6
  • 配列要素の繰り返し処理(for of と同じ)
  • コールバック関数を切り離して記述できる
const numbers = [1, 2, 3, 4, 5]
let sum = 0

const adder = (number) => (sum += number)
numbers.forEach(adder)
console.log(sum)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQuery checkboxの流れ

今回、チェックをつけた項目をアラートに表示させる実装をしました。
備忘録として、コードの流れを記録します。

590baee4c13d52510a51f710c028910d.png

コード全体はこう

$(function() {
  $('form').on('submit', function(e) {
    let output = ''; 
    let checkboxes = $(this).find('input[type="checkbox"]');
    checkboxes.each(function(i, checkbox) {
      checkbox = $(checkboxes[i]);
      if (checkbox.prop('checked')) {
        output += checkbox.attr('value') + '\n';
      }
    });
    e.preventDefault();
    alert('あなたが選んだ果物:\n' + output);
  });
});

①まずリロードした際に実行されるよう関数で囲む

$(function() {
});

②formタグのsubmitに実装する

$(function() {
 $('form').on('submit' function(){
 });
});

③preventDefault()の引数にeを指定し、デフォルトで設定されているイベントをキャンセルする。

$(function() {
 $('form').on('submit' function(e){
  e.preventDefault();
 });
});

④最終的に表示したい値と、空の変数を用意

$(function() {
 $('form').on('submit' function(e){
  let output = '';
  e.preventDefault();
  alert('あなたが選んだ果物:\n' + output);
 });
});

⑤イベントで取得する値をinput要素のcheckboxに指定し、変数checkboxesに代入

$(function() {
 $('form').on('submit' function(e){
  let output = '';
  let checkboxes = $(this).find('input[type="checkbox"]');
  e.preventDefault();
  alert('あなたが選んだ果物:\n' + output);
 });
});

⑥チェックボックスの値をバラして取得するために、functionの引数にi(index)とcheckboxを指定。

$(function() {
 $('form').on('submit' function(e){
  let output = '';
  let checkboxes = $(this).find('input[type="checkbox"]');
 checkboxes.each(function(i, checkbox) {});
  e.preventDefault();
  alert('あなたが選んだ果物:\n' + output);
 });
});

⑦if文でoutputに洗濯したvalueを代入して完成

$(function() {
  $('form').on('submit', function(e) {
    let output = ''; 
    let checkboxes = $(this).find('input[type="checkbox"]');
    checkboxes.each(function(i, checkbox) {
      checkbox = $(checkboxes[i]);
      if (checkbox.prop('checked')) {
        output += checkbox.attr('value') + '\n';
      }
    });
    e.preventDefault();
    alert('あなたが選んだ果物:\n' + output);
  });
});

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

PHPのdate()やmktime()をjavascriptでも使いたい

同様の処理を行うライブラリとしてはdayjsやMoment.jsがメジャーかと思いますが、個人的にはあれほど多機能なものは要らなかったので作成してみました。

toLocaleDateString()toLocaleTimeString()等でも色々な表現はできますが、個人的にはやっぱり使い慣れたフォーマット文字列での指定が楽です。

基本的にPHPのdate()mktime()とほぼ同様に扱えるかと思いますが、以下の既知の相違があります。

mktime()

第7引数 is_dst はありません。
第6引数 year にマイナスの値を渡した場合の結果がPHPのmktime()とは異なります。

date()

フォーマット文字は個人的に必須のもの(YmdHis)といくつかの簡単そうなもののみの実装で、PHPで使えるものすべてには対応していません。
PHPのdate()ではフォーマット文字を\でエスケープすることで置換せずそのまま表示させることができますが、本スクリプトでは未対応です。

参考

PHP mktime マニュアル
PHP date マニュアル

スクリプト

mktime_date.js
/**
 *  日付をUNIXタイムスタンプに変換
 */
function mktime(...args) {

    const
        d = new Date(),

        // 引数受け取り
        hour   = args[0] ?? d.getHours(),
        minute = args[1] ?? d.getMinutes(),
        second = args[2] ?? d.getSeconds(),
        month  = args[3] ?? d.getMonth() + 1,
        day    = args[4] ?? d.getDate(),
        year   = args[5] ?? d.getFullYear(),

        // 年指定補正
        _year = year < 70 ? 2000 + year :
                year < 100 ? 1900 + year : year;

    try {
        // 引数に数値でないものが含まれる
        if( isNaN(hour) ||
            isNaN(minute) ||
            isNaN(second) ||
            isNaN(month) ||
            isNaN(day) ||
            isNaN(year)
        ) throw new Error('A non-numeric value was specified for the argument');
    } catch(e) {
        console.error(e.message);
    }

    // 日時設定
    d.setFullYear(_year, month -1, day);
    d.setHours(hour, minute, second);

    // 戻り値
    return Math.floor(d.getTime() / 1000);
}

/**
 *  日付/時刻を書式化
 */
function date(format, timeStamp) {

    // timeStamp未指定の場合は現在日時のタイムスタンプを設定
    if(timeStamp === undefined)
        timeStamp = Math.floor(Date.now() / 1000);

    try {
        // format未指定
        if(format === undefined)
            throw new Error('Argument `format` not specified');

        // timeStampが数値ではない
        if(isNaN(timeStamp))
            throw new Error('A non-numeric value was specified for the argument');
    } catch(e) {
        console.error(e.message);
    }

    // 日時取得
    const
        d      = new Date(timeStamp * 1000),
        df     = new Date(timeStamp * 1000),
        year   = d.getFullYear(),
        month  = d.getMonth() + 1,
        day    = d.getDate(),
        hour   = d.getHours(),
        minute = d.getMinutes(),
        second = d.getSeconds(),
        w      = d.getDay(),
        mDays  = monthLastDays(year);

    // 1月1日からの日数
    df.setMonth(0);
    df.setDate(1);
    const days = (d - df) / 864e5;

    // 戻り値
    // 不要なreplaceメソッドチェーンがあれば
    // 適宜コメントアウトや削除をしてください
    return String(format)
        // 年 4桁
        .replace(/Y/g, String(year).padStart(4, '0'))
        // 月 2桁
        .replace(/m/g, String(month).padStart(2, '0'))
        // 日 2桁
        .replace(/d/g, String(day).padStart(2, '0'))
        // 時 24時間単位 2桁
        .replace(/H/g, String(hour).padStart(2, '0'))
        // 分 2桁
        .replace(/i/g, String(minute).padStart(2, '0'))
        // 秒 2桁
        .replace(/s/g, String(second).padStart(2, '0'))

        // 年 2桁
        .replace(/y/g, ('0' + year).slice(-2))
        // 月 桁揃えなし
        .replace(/n/g, month)
        // 日 桁揃えなし
        .replace(/j/g, day)
        // 時 24時間単位 桁揃えなし
        .replace(/G/g, hour)
        // 時 12時間単位 2桁
        .replace(/h/g, String((hour + 11) % 12 + 1).padStart(2, '0'))
        // 時 12時間単位 桁揃えなし
        .replace(/g/g, (hour + 11) % 12 + 1)
        // 閏年か 0:閏年ではない 1:閏年
        .replace(/L/g, mDays[1] == 29 ? 1 : 0)

        // 曜日 0:日~6:土
        .replace(/w/g, w)
        // 曜日 1:月~7:日
        .replace(/N/g, (w + 6) % 7 + 1)
        // 年間通算日 0~365
        .replace(/z/g, days)
        // 月の日数
        .replace(/t/g, mDays[month - 1])

        // 文字誤置換防止 下処理
        .replace(/(\w)/g, ":$1:")
        // 曜日 3文字
        .replace(/:D:/g,
            ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][w])
        // 月 3文字
        .replace(/:M:/g,
            ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][month - 1])
        // 曜日 フルスペル
        .replace(/:l:/g,
            ['Sunday',   'Monday', 'Tuesday', 'Wednesday',
             'Thursday', 'Friday', 'Saturday'][w])
        // 月 フルスペル
        .replace(/:F:/g,
            ['January',   'February', 'March',    'April',
             'May',       'June',     'July',     'August',
             'September', 'October',  'November', 'December'][month - 1])
        // am/pm
        .replace(/:a:/g, hour < 12 ? 'am' : 'pm')
        // AM/PM
        .replace(/:A:/g, hour < 12 ? 'AM' : 'PM')
        // 誤置換防止処理 後始末
        .replace(/:(\w):/g , "$1")
}

/**
 *  該当年の各月末日を返す
 */
function monthLastDays(year) {
    return [31, (!(year % 4) && (year % 100) || !(year % 400)) ? 29 : 28,
            31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
}

/**
 *  mktimeへ年月日時分秒の順に引数を渡すラッパー
 */
function dateToTime(...args) {
    return mktime(
        args[3], args[4], args[5],
        args[1], args[2], args[0]);
}

実行サンプル

// example

// Date.now()でタイムスタンプ取得
time = Math.floor(Date.now() / 1000);
console.log( time ); // 1594489402
// mktime()へ引数なしでのタイムスタンプ取得
time = mktime();
console.log( time ); // 1594489402

// 実装した全フォーマット文字
formatStr = 'Y-m-d H:i:s y w z t D M l F a A G h g L';
console.log( date(formatStr, time) ); // 2020-07-12 02:43:22 20 0 193 31 Sun Jul Sunday July am AM 2 02 2 1
console.log( date(formatStr) ); // 2020-07-12 02:43:22 20 0 193 31 Sun Jul Sunday July am AM 2 02 2 1

console.log( date('Y-m-d H:i:s D', time) ); // 2020-07-12 02:43:22 Sun

time = mktime(0, 0, 0, 1, 1, 2000);
console.log( date('Y-m-d H:i:s D', time) ); // 2000-01-01 00:00:00 Sat

time = mktime(4, 5, 6, 12, 3, 2000);
console.log( date('Y-m-d H:i:s D', time) ); // 2000-12-03 04:05:06 Sun

time = mktime(0, 0, 0, 2, 29, 2020);
console.log( date('Y-m-d H:i:s D', time) ); // 2020-02-29 00:00:00 Sat

time = mktime(0, 0, 0, 2, 29, 2019);
console.log( date('Y-m-d H:i:s D', time) ); // 2019-03-01 00:00:00 Fri

time = mktime(0, 0, 0, 1, 0, 2000);
console.log( date('Y-m-d H:i:s D', time) ); // 1999-12-31 00:00:00 Fri

time = mktime(0, 0, -1, 1, 1, 2000);
console.log( date('Y-m-d H:i:s D', time) ); // 1999-12-31 23:59:59 Fri

time = mktime(24, 0, 0, 1, 1, 2000);
console.log( date('Y-m-d H:i:s D', time) ); // 2000-01-02 00:00:00 Sun

console.log( date('Y-m-d H:i:s D', 0) ); // 1970-01-01 09:00:00 Thu

console.log(date('Y-m-d H:i:s', mktime(123, 456, 789, -123, -4567)) ); // 1997-03-05 10:49:09

// dateToTime()はmktime()へ年,月,日,時,分,秒の順で引数を渡すラッパーです
console.log(date('Y-m-d H:i:s', dateToTime(2020, 1, 2, 12, 34, 56))); // 2020-01-02 12:34:56
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SVGを書くのが面倒なので入力補完できるようにしてみる

 「素直にInkscapeとかイラレ使えば?」で片付く話なんですけども...お付き合いいただきたい。

これ ↓ 作ってるときの話です。
GitHub: mafumafuultu/svg.js dev

SVG書きたい!主要タグは知ってる!...属性...うっ...頭が...

IDEなどでは割とHTMLの属性の補完は効くんですが、SVGになるとどうも弱い。
<path>のd属性の書き方とか、シェイプ・画像・テキストレンダーの品質の設定とかそこそこ忘れるし,
<line>x1 y1 x2 y2属性とか書くの面倒くさいですね。
<polyline>points属性も、長くなると数値の羅列の迷路をさまようことになり、
「こっちのN番目と、こっちの N'番目を入れ替えて、ここのN''番目とN'''番目をN''''番目の後ろに...今、何番目....?」と迷子になりがち(個人差あり)

問題点

  • スペルの長い属性なんか覚えてない。故に何を値に設定すればいいとかわからない。
  • ある属性がついていたら、この属性を有効にして、こっちの属性は無視とかがあり面倒
  • 座標の区切りが見づらい

入力補完があって、座標も見やすくなれば大抵のことは解決するんだけども...うーむ...

JSで書けばいい

だって
jsdoc書けば、あいつら補完してくれるし
image.png
メソッドチェインの仕組み用意すれば、候補出るし
image.png
Arrayに座標をダラダラと書いておけば見やすいし、加工も楽なのでは?
image.png

条件

  • Vanilla JS
  • メソッドチェイン
    this返せばいいよね
  • document.createElementNSを何度も書くのは嫌
    → タグ名受け取って、そのタグ名のSVG要素を返すものがほしい。
  • 変数いっぱい作ってappendを何度もタイプするのは嫌
    → 子要素を受け取って自身にappendする関数を用意する
  • タグ名書いたら必須の属性を教える
    line(x1, y1, x2, y2) な感じでタグのチェイン開始時に必要なものを指定しないと動かないようにする
  • 新しい属性が出てきても対応できるようにする
    → 属性の関数を用意するのではなくattrsとか用意してObjectを渡して処理
  • <path>のd属性ってぱっと見わからない
    → d属性を作るチェインを作って<path>に食わせる?

etc.etc...

 これらをうまいことまとめるには
 SVGの要素を状態として持ち、その状態を変化させることに特化した仕組みを組んで、その仕組みに対してPrototype拡張してしまえばいいと考えた。

Prototype(疑似)拡張

で、とりあえずそんなものを組んでみることにした。

  • タグ名渡して、そのSVG要素を返す関数
  • 要素を状態として持つオブジェクト
  • Prototypeとして、すべての要素に用意したい機能
  • タグごとにほしい機能

この辺りは Object.createObject.assign で解決できる

/*
ネームスペース付きのcreateElementは何度も書きたくない
ついでに、HTMLタグも作れるようにした。
*/
const _svgTag = tag => document.createElementNS('http://www.w3.org/2000/svg', tag);
const _tag = tag => document.createElement(tag);

/* 共通でほしい機能 */
const __BASE_PROTO__ = {
    $: {
        value(...child) {
            return /* 状態として持った要素に子要素を追加 */ this;
        }
    },
    attrs: {
        value (o) {
            return /* 属性をセットする */ this;
        }
    },
    /* 略 */
};

var wrapper = function(el custom) {
    return Object.create(
        {'@': el}, // タグを状態として持つオブジェクト
        custom instanceof Object
            ? Object.assign({}, __BASE_PROTO__, custom) /* タグごとにほしい機能 */
            : __BASE_PROTO__
    );
};

基本の仕組みを作ってしまえば50%は完成したようなものなので残りの50%を埋めよう。

完成予定を確認

これを

svg(200, 200).$(
    group().attrs({stroke: 'orange'}).$(
        line(0,0,200,200).attrs({id: "foo"}),
        polyline([[0, 50], [40, 0], [90, 50]]),
        path().start(50, 50, true).line(100, 100).line(50, 100).line(50, 150).close()
    )
);

こうしたい

<svg width="200" height="200" viewBox="0 0 200 200" version="1.2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <g stroke="orange" stroke="orange">
        <line x1="0" y1="0" x2="200" y2="200" id="foo" />
        <polyline points="0,50 40,0 90, 50" />
        <path d="M 50 50 l 100 100 , 50 100 , 50 150 Z" />
    </g>
</svg>

基本のSVGタグを用意する。

 <svg> <line> <rect> <circle> あたりは確実にほしい。

const svg = (width=400, height=300, viewBox = `0 0 ${width} ${height}`) => wrapper(_svgTag('svg')).attrs({version:1.2,xmlns :"http://www.w3.org/2000/svg", 'xmlns:xlink': 'http://www.w3.org/1999/xlink',width, height, viewBox});
const line = (x1=0,y1=0,x2=0,y2=0) => wrapper(_svgTag('line')).attrs({x1,y1,x2,y2});
const rect = (x=0, y=0, width=0, height=0) => wrapper(_svgTag('rect')).attrs({x, y, width, height});
// その他、基本シェイプも作る

<line>だけで書くのもつらいので <polyline> もほしい

const joinPos = ([x, y]) => `${x},${y}`;
const points = (...p) => p.map(joinPos).join(' ');
const polyline = (...p) => wrapper(_svgTag('polyline')).attrs({points: points(...p)});

<path><animate> はちょっと毛色が違う。
d属性は相対位置指定、絶対位置指定、ベジエ曲線の指定は直前の指定によって値を省略などがあってなかなか面倒ですし、arc(円弧)とか指定が多いので大変です。

const path = () => ({
    before: '',
    d : [],
    __ (s, abs = false) {return n = abs ? s.toUpperCase() : s, this.before === n ? `, ` : (this.before = n , `${n} `);},
    __cache (v, abs, mk) {return this.d.push(`${this.__(mk, abs)}${v}`), this;},

    start (x, y, abs) {return this.d.length = 0, this.move(x, y, abs);},
    move(x, y, abs) {return this.__cache(`${x} ${y}`, abs, 'm');},
    line(x, y, abs) {return this.__cache(`${x} ${y}`, abs, 'l');},
    /* 略 */
    /* パスを閉じてタグを吐くか、d属性の値を吐けるように */
    close(attr={}, close=true) {return wrapper(_svgTag('path')).attrs({...attr, d: this.d.join(' ') + (close ? ' Z' : '')});},
    toPath(close=true) {return this.d.join(' ') + (close ? ' Z' : '');}
});

 SVGにはアニメーション系のタグがありパスに沿ってアニメーションをすることができるので、そこで使うこともできるように path()でd属性の値を吐けるようにしています。

 これで基本図形・パスをかけるようになりました。
ここまでできれば65%ですかね。

アニメーション系、レンダー、<g> <def>
意外とはまりそうな<use>などは次回以降に回して今回は終わります。

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