• 作成:

簡単なアドレス帳のレポートを見直してweb技術と自分のwebに対する姿勢の変化を観察する

背景

2015年06月のオブジェクト指向技術の第1回レポートで, 簡単なアドレス帳というものを書きました. 私がまともにwebを学ぶ以前に書いたものなので, 公開するのは気恥ずかしいですが, 死蔵しておくのも勿体ないので, 見直すことでweb技術と自分のwebに対する姿勢の変化を観察したいと思います.

手抜きのためにレポートの内容を改訂して書いているので, 一部不自然な文章になっています. ご了承ください.

最終的なソースコード

簡単なアドレス帳の実装

簡単なアドレス帳を作る.

何故jQueryを使っているか

私はjQueryが嫌いで, なるべく使いたいとは思っていませんでしたが, レポートの指定としてjQueryを使うことというものがあったので, 仕方なく使いました.

しかし, まだまだjQueryを使っているWebページは多いため, 新規開発にjQueryが不要になっても, アップデートにjQueryの知識は依然必要となります.

なので, そこまでモチベーチョンは下がりませんでした.

XSS対策

この課題の裏ポイントはXSS対策だと予想しました. ユーザが自由にデータを入力できるWebアプリケーションでは, XSS対策はつきものです.

これまでの講義でinnerHTMLを使ってきたため, そのままinnerHTMLを使う人のシステムには名前が<script>document.body.innerHTML = "";</script>さんのアドレスが登録されてしまうなどというというトラップがあると想像しました.

幸い「<b>などの一部のタグは使えるようにする」といった指定は存在しなかったため, 対処は容易でした.

innerText(textContent)/innerHTMLを使ったHTMLエスケープは充分でないので今すぐやめろ、お前たちはもう終わりだ - TODESKINGを参考にして, document.createTextNodeを使うことにしました.

入力インターフェイス

入力データをJavaScriptで取り出すには, 入力インターフェイスが必要です. HTMLで入力を取り扱うには, <form><input>を使うのが普通です. 入力項目は複数あるので, <dl>リストを使って列挙します.

複数の入力項目をJavaScript側で区別するために, それぞれに名前をつける必要があります. 命名法については, 既存の規格であるHTML Standardに個人の情報を表すhCard 1.0.1 XMDP profileがあるので, それに従うことにしました.

<input>の型には種類があり, 誕生日にはDateタイプを使う必要があるため, 専用の<input type="date">を使えば, 日時入力のインターフェイスブラウザが勝手に準備してくれると予想していましたが, 調べてみたところそれは当時Chromiumだけでした. Firefoxは54からフラグ付きで対応を始めるようですね, webが段々と進化していっているのがわかります.

後はJavaScript側から参照しやすいように, idを#preEditと割り振り, 入力インターフェイスは完成します.

出力インターフェイス

JavaScriptからデータを出力するために, 出力インターフェイスが必要です. 今回は表形式で出力するという指定があるため<table>を使います. 当時は<table>が大嫌いで, 絶対使いたくないなどと思っていましたが, 今はレイアウトに使わないなら許容しています. 本来項目ヘッダは可変の方が望ましいですが, 簡単なアドレス帳のため, 項目ヘッダをベタ書きしました.

idを#contactと割り振り, 出力インターフェイスの完成です.

入力されたデータの取り出し

<form id="preEdit">のデータ送信イベントを乗っ取り, 処理を開始します. 取得したデータをJavaScript側で扱いやすいように, Mapオブジェクトに格納します.

<input type="date">指定は実際には自動でDateに変換をしてくれなかっため, ここで変換します.

EcmaScript6のarrow functionやArray.reduceと言った構文はChromiumでは動きませんが, 実務ではbabelやAltJSを使うため, 問題にはならないでしょう. なんて, 当時はそう思っていましたが, ArrayジェネリックメソッドはFirefoxでも削除されてしまうようですね. 非標準の Array/String 汎用メソッドが廃止予定となりました | Firefox サイト互換性情報こちらが死ぬとは思っていませんでした…

今は[].reduce.callのように書いています. Array.fromの方が良いでしょうか.

データの出力

Mapに変換したデータを, 出力のためにHTMLの<table>の1行分の形式に変換します. XSS対策のエスケープは出力時に行うべき「クロスサイトスクリプティング対策」でGoogle検索して上位15記事を検証した | 徳丸浩の日記なので, ここでcreateTextNodeを使います.

都道府県で絞り込み

都道府県で絞り込みができるようにする.

データバインディング

この課題のポイントは, データバインディングが適切に出来るかどうかだと考えました. Vue.jsなどのフレームワークを使えば実現は容易ですが, 今回はそういうものは使わないので, データの変更を適切に捕捉出来るかどうかが鍵となるでしょう.

Object.observeを使えば容易ですが, Firefoxがサポートしてなかったので使いません. 残念ながらbabelもサポートしていませんでした.

Object.observeが死んでしまいましたね… 当時はデータバインディングはVue.jsぐらいしか知らず, Object.observeによるネイティブバインディングに期待していました. 今はReactに夢中です. 数年後はどうなっているでしょうか.

フィルタリング条件入力インターフェイス

フィルタリング条件を入力するインターフェイスが必要です. <select>を使うことは既に指定されているので, 追加するものは自明です.

contactの表示の更新

今回はテーブルをフィルタリングする必要があるため, ステートレスに表示しています. テーブル表示を更新するときは関数updateContactを呼ぶことにしています. 関数updateContactでは, <select id="filterPred">タグの内容に従って連絡帳をfilterします.

当時から状態を考えるのが面倒くさかったのか, DOMの吐き出しをステートレスで行っていますね. これはNativeで行っているので効率が悪いですが, 今はReactという便利なものがあるので安心ですね.

フィルタリングメニューの作成

都道府県が追加されたら呼ばれるupdateRegionPredで, プルダウンメニューを作成します.

イベントのバインド

フィルタリング条件<select id="regionPred">が変更されれば, updateContactで表示を更新します.

感想

開発中に引数の型の不一致でエラーが発生することが多く, 私はやはり静的型, せめて型アノテーションを備えているAltJSの導きを待望していました.

誕生日でソート

誕生日でソートできるようにする.

課題のポイント

Dateオブジェクトのソートが出来るかどうかだと考えました.

ソートインターフェイス

ソート用のボタンを追加します. 先ほどのボタン用にイベントを追加して, クリックされたらソートをする関数sortContactByBdayを呼び出すようにします.

DateはUnix Timeに変換すれば普通に数値として比較できます. Unix TimeはgetTime()で取得できます. ソートした後更新をHTML側に反映する必要があるので, updateContact()します.

感想

実装した後に, 1970年より前に生まれた人を全く考えていなかったことに気がつきまして, これでは偉い人の連絡先を記録できないと慌てましたが, JavaScriptのDateオブジェクトは, 1970年以前の年月は負のUnix Timeとして表現するので, 問題ないようでした. また, うるう秒などの処理はあくまでこれが比較であるから問題はないでしょう. しかし, 繊細な用途にUnix Timeは向いていないな, と思いました.

年齢の表示

現在何歳であるかを表示するようにする.

年齢縦列の追加

新たに年齢を表示するため, 縦列として年齢を追加します.

データ型の定義

それまでは特性上Mapオブジェクトを使っていましたが, 課題の条件でメソッドを定義する必要があるので, 独自のオブジェクトを定義して, これまでgetsetを使っていたのを, プロパティアクセスに書きかえます.

メソッド定義には, Arrow Functionはレキシカルにthisを束縛するので無名functionを使います.

contactToRowに年齢表示の処理を追加して完了.

感想

Dateオブジェクトに, 時間の差分を計算したり, 単位を変換するメソッドがなかったため, 自分で割り算を行うしかありませんでした.

C++のstd::chronoなどは単位を楽に扱えるのですが.

多分これは, JavaScriptのDateはあくまでもカレンダー上の日時を取り扱うことを目的として, 汎用的な時間を扱うことを目的とはしてないからなのでしょう.

なんて書いてますが, 今MDNを見たら普通に差分計算や単位変換が出来ますね. 自分が見逃していたのか, いつの間にか進歩していたのか…

WebStorageによるデータの保存

その時, 自分用のpixiv用ユーザースクリプトが設定保存インターフェイス実装前で忙しくて放置されているのを思い出したので, それ用の学習も兼ねて, WebStorageによるデータの保存を実装することにしました.

そのユーザスクリプトは今でも放置されています. というか最近pixivをあまり見ないので使わなくなってしまいました…

型変換

JSONにするとDate型情報が消滅するので, Contactコンストラクタで変換しています.

初期読み込み

contactCacheの定義時に初期化します. 保存してない時はlocalStroage.getItemnullを返却しますが, Array.mapnullを受け付けないので, 空オブジェクトでも渡しておきます. ここでJSONからContactへ型変換もしておきます.

読み込み後に更新

データが読み込まれたらupdateContactで再設定します.

考察

WebStorageは文字列しか保存できないので, JSONなどにする必要がありますが, JSONにすると型情報は消滅してしまうため, いちいち変換作業が必要になります.

__proto__を変更するのは非推奨ですし, したとしても, ディープに型情報を保存しているものは対応できません.

WebStorageに保存することを前提とするデータには, プロトタイプは付加せずに, JSONと完全に互換性のある, 単純なObjectとして扱った方が良いことがわかりました.

なんかうまい感じに標準化されたJSONよりもうちょっとリッチなシリアライズ方法は無いのでしょうかね. evalするのは流石に気が引けます.