JavaScript : してログ

12

PHP + JavaScript でシステムを組む場合、PHP 側の設定値を JavaScript 側に渡したいときがあります。いくつも方法があり簡単ではありますが、どうもスマートでないやり方になってしまうのでどうにかしたいと思っていました。

代表的な方法は、テンプレートエンジンに埋め込む(JavaScript を埋め込むか、input タグに入れる、class 属性などに突っ込む)、ajax で json を取得させるなどがあります。前者はカッコ悪いの一言。後者は非同期通信になってロード完了するまで、それらの値にアクセスできないなどイマイチです。そこで少し検討してみました。

JavaScript を吐き出す PHP を書いてしまう

JavaScript のコードを吐き出す PHP を書いて、ヘッダーの script タグでロードするという方法です。これならテンプレートが複雑にならないし、ロードのタイミングで悩むこともありません。ただし、PHP 側のコードが汚くなるのが目に見えるので、もう少し検討してみました。

せっかくなので JSON を受け渡しに使いたい

PHP 側で JSON を作り JavaScript 側は ajax で受け取る、この流れ少し変えて JSON データをデコードするだけの JavaScript を書いてロードしてもらう、というふうにします。

  1. PHP で受け渡したいデータを配列などにまとめる
  2. データを JSON にエンコードして base64 エンコードする(エスケープしなくて良くするため)
  3. JavaScript で base64 → JSON の順でデコードするコードに加工する
  4. JavaScript として返却する

このような流れでコードを書けば、PHP、JavaScript でコードが入り乱れることは無くなり非常にすっきりすると思います。簡単なのでコードを例示する必要も無いかと思いますが、一応書いておきます。

PHP 側のコード($data に受け渡すものは準備済みとする)
$json = json_encode($data);
$b64 = base64_encode($json);
$js = "var data=JSON.parse(window.atob('".$b64."'));";
header('Content-Type: text/javascript; charset=UTF-8');
echo $js;
HTML(JavaScript)側のコード
<script type="text/javascript" src="config.js.php"></script>

ネットで探しても、シンプルで使い勝手が良いカレンダー作成関数が無かったので自作してみました。そのまま使えて、余計なコードが入って無くて、カスタマイズもしやすい、そんな関数になっていると思います。

カレンダー作成関数
function build_calendar(mon,cell) {

	var cur = new Date(mon.getTime());
	var lst = new Date(mon.getTime());

	cur.setHours(0,0,0,0);
	cur.setDate(1);
	cur.setDate(-cur.getDay()+1);

	lst.setMonth(mon.getMonth()+1,0);
	lst.setDate(lst.getDate()+7-lst.getDay()-1);

	var tbody = '';

	tbody += '<table>';
	tbody += '<tr><th>日</th><th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th></tr>';

	while (cur<=lst) {

		tbody += (cur.getDay()==0 ? '<tr>' : '');
		tbody += '<td>'+cell(cur)+'</td>';
		tbody += (cur.getDay()==6 ? '</tr>' : '');

		cur.setDate(cur.getDate()+1);
	}

	tbody += '</table>';

	return tbody;
}

使い方

第1引数に作成したい月の Date オブジェクト(月内であれば何でも良い)を、第2引数で日付セルのコールバック関数を渡してください。セルをコールバック関数にしたことで、予定を入れたり、月齢を入れたり、自由にカスタマイズができます。

var html = build_calendar(new Date('2019/1/1'),function(cur){
	return cur.getDate();
});
出力結果
303112345
6789101112
13141516171819
20212223242526
272829303112

コールバック関数は引数で Date オブジェクトを渡してくるので、シンプルに getDate() で日付を返せば出力結果のようになります。その Date オブジェクトを別の関数(たとえば月齢計算など)に渡せば、情報をカレンダーに埋め込むことが可能になります。オプションを増やして、デフォルトの表示を付けたり、月内の日付以外は出さないようにしたり、汎用的に拡張してもいいかも知れません。

ロジック

コードを見れば分かる程度のことですが、簡単にロジックを説明しておきます。

  1. 渡された日付からその月の1日を求め、更にその週の日曜日(A)を求める
  2. 渡された日付からその月の末日を求め、更にその週の土曜日(B)を求める
  3. (A)から(B)までループさせながらテーブルタグを構成していく
  4. 各日付の中身は、渡されたコールバック関数を呼び出して任せる

シンプルにするための工夫としては、半端な前月と次月の日付を含めてループさせることで余計なパディングをしていないこと、<tr> タグの入れ方により変数の数を減らした点です。カレンダーのカスタマイズは CSS とコールバック関数でほとんどクリアできると思います。

jQuery UI Dialog で modal: true としたダイアログで、再表示したときに操作不能になるという現象に遭遇しました。 操作不能というのは、ダイアログに配置したセレクトボックスは選択リストが表示されなくなり、テキストボックスはクリックしてもキャレットすら表示されないという状態です。 ただし、フォーカスは入っているらしく Chrome などで動かすと、枠の色が変わるのが分かります。 ちなみに、モーダルでなければそういうことにはなりません。

色々と調べた結果、作りおきのダイアログがあるとダメなことが分かりました。 問題のあったページでも、そのダイアログとは別に、autoOpen: false として作っておいて、必要なときに dialog('open') しているものがありました。 これを、ボタンやリンクがクリックされた際に、都度作るようにしてみたところ、問題が解決しました。 この現象でお悩みの方は、作りおきのダイアログが他に無いか調べてみてください。

OpenLayers をバージョンアップしたところ、jQuery の draggable を適用したフローティング・パネルの動きが変になってしまいました。ゆっくりと動かして、ドラッグハンドルをマウスが外れなければ正しく動きますが、速く(といっても普通に)動かして、ドラッグハンドルをマウスが少しでも外れると、ドラッグが解除されたり、おかしな動作が始まります。マウスダウンからアップまで、ハンドリングできない状態になり、低スペックマシンではかなり操作しづらくなります。

なかなか原因が分からなくて、旧バージョンにロールバックしようかと思いましたが、分かってしまえば至って単純な原因でした。OpenLayers の新しいバージョンは、fallThrouth というオプションがデフォルトで false になっています。これを true にするだけでした。

	map = new OpenLayers.Map({
		fallThrough:       true
	});

FormData があると Ajax でファイルアップロードできるようになって大変便利なのですが、困るのが IE8 と IE9 の対応です。 IE10 からは FormData が使えますが、未だに IE8 の要求は来ます。 かつての IE6 のポジションを受け継いでいる臭が漂っています。

諦めて POST で画面リロードするところでしたが、ちゃんと調べて見ると代替策がありました。 使うのは POST ですが、見せ方としては非同期通信っぽく動きます。 サンプルコードは載せませんが、要点を箇条書きにしますので、参考にしてください。

  • 親ページに iframe を配置して見えないようにします(display:none だと Safari でまずいみたいなので、横幅&縦幅をゼロにします)
  • フォームのターゲット要素(target="<iframe名>")で、配置した iframe を指定します
  • サーバー側で POST を受け取ったら、JavaScript で親ページの関数を呼んで通知するようにします
  • 親ページでは関数が呼ばれたら、アップロード完了などのメッセージを提供できます

最近のブラウザは、showModalDialog で開いたウィンドウ内の遷移は普通に行えます。 しかし、IE8 では新しいウィンドウが開いてしまします。 ググると、iframe を入れて面倒なことして回避している輩もおられるようですが、そんなことしたくないですよね。 いろいろと調べてみると、海外のサイトに解決方法を見つけました。

IE8 はどうやらベースとなるターゲットが親ウィンドウかなんかに指定された状態で、ダイアログがオープンされているようです。 html のヘッダー部分に、ベース・ターゲットを自分自身にするよう指定すれば良いみたいです。

ベースターゲットの指定
<base target="_self" />

「オブジェクトでサポートされていないプロパティまたはメソッドです。」

この呪文の詠唱を IE が始めたら、とりあえずマウスぶん投げて甘いもんでも補給しましょう。 さて、今回は trim が原因なのですが、なんと IE8 は実装されていません。 このような基本的な、文字列操作が実装されていないなんて、はっきり言って思いもよりませんでした。 まさに(・・)です。

ならば、正規表現なんかを使って解決してもいいのですが、jQuery が使える状態なら、下記のようにすれば良いと思います。 少し前なら IE6 がー、とか言ってたけど、IE8 お前もなのか。。。 Microsoft は間違えなく、インターネットの発展の足を引っ張ってると思います。

IE8 で動かないコード
var after = before.trim();
jQuery での代替
var after = $.trim(before);

普通に alert すると [object HTMLDocument] なんて感じで何の役にも立たないですので、オブジェクトのプロパティをダンプする関数を作ってみました。alert の代わりに使用すると、オブジェクトの見通しが良くなって開発効率が上がりそうです。

function obj_dump(obj) {
	var txt = '';
	for (var one in obj){
		txt += one + "=" + obj[one] + "\n";
	}
	alert(txt);
}
テスト
12