2011年11月8日火曜日

strftimeで日付がずれる

もしかしたら有名な話なのかもしれませんが、今日 スケジュールの画面のデモをphpで作っていたときにちょっとはまった。 クライアントにイメージをつかんでもらうための簡単なカレンダを phpでループさせて作っていたのだが、デモプログムの一部で、
date_default_timezone_set('Asia/Tokyo');
$base = strtotime('2011-11-01');
$days = explode(',', '日,月,火,水,木,金,土');
for ($i=0; $i<30; ++$i) {
 $date = $base+24*60*60*$i;
 $mmdd = strftime('%m/%d', $date);
 $day = $days[strftime('%w', $date)];
 echo "<br>$i: $mmdd($day)";
}

みたいに、今月の頭からループで24時間づつ増やしながら日付を表示させて、カレンダの日付の部分を表示させようとしたら、想定外の出力に!

0: 11/01(火)
1: 11/02(水)
2: 11/03(木)
3: 11/04(金)
4: 11/05(土)
5: 11/06(日)
6: 11/06(日)

7: 11/07(月)
8: 11/08(火)
9: 11/09(水)
10: 11/10(木)
: 
打ち合わせ開始まで30分しかなかったので、軽いパニックに。
誤差が蓄積されたような動きであったので、とりあえず、その場は
$base = strtotime('2011-11-01 14:00');
変更したところ、想定の出力で動き、残りの部分のコーディングに戻ることができた。

気になったので、今、「夏時間」をwikipediaで調べたら、


アメリカ合衆国(一部除く。前述のとおり2007年から次のように変更され実行されている)、カナダ(一部除く)、メキシコ(一部除く) - 3月第2日曜日午前2時〜11月第1日曜日午前2時(現地時間基準。開始日には2時が3時になり(1時59分59秒の次が3時00分00秒)、終了日は2時が再度1時(1時59分59秒の次が1時00分00秒)になるため、開始日は1日が23時間、終了日は逆に25時間になる)
と、アメリカでは11/6は25時間あることがわかった。ちなみに前のスクリプトに時間まで表示してみると、
0: 11/01 00:00(火)
1: 11/02 00:00(水)
2: 11/03 00:00(木)
3: 11/04 00:00(金)
4: 11/05 00:00(土)
5: 11/06 00:00(日)
6: 11/06 23:00(日)

7: 11/07 23:00(月)
8: 11/08 23:00(火)
9: 11/09 23:00(水)
10: 11/10 23:00(木)
11/6の00:00に24時間を足すと、11/6の23:00になっている。


ああ、すっきり!!、でもさ、夏時間のない'Asia/Tokyo'のロケールのはずじゃん。まいっか。いや、よくないな、よくない。
ここで、デジャヴが、どっかの環境で'Asia/Tokyo'が効かなくて、GMT-9って指定したことあったっけ。

 date_default_timezone_set('Etc/GMT-9');
 $base = strtotime('2011-11-01');
 $days = explode(',', '日,月,火,水,木,金,土');
 for ($i=0; $i<30; ++$i) {
  $date = $base+24*60*60*$i;
  $mmdd = strftime('%m/%d  %H:%M', $date);
  $day = $days[strftime('%w', $date)];
  echo "<br>$i: $mmdd($day)";
 }
 結果は
0: 11/01 00:00(火)
1: 11/02 00:00(水)
2: 11/03 00:00(木)
3: 11/04 00:00(金)
4: 11/05 00:00(土)
5: 11/06 00:00(日)
6: 11/07 00:00(月)

7: 11/08 00:00(火)
8: 11/09 00:00(水)
9: 11/10 00:00(木)
10: 11/11 00:00(金)
と、よしっ!きれいだ。ってなんでAsia/Tokyoと結果が違うの?
あ、そっか。日本って震災の節電の関係で夏時間設定されたんだっけ。
なんか違う。眠いからに違いない。寝よう。

よし、リフレッシュ。

ソースを少し変えてタイムゾーンを変えて流せるようにした。

echo "<pre>\n";
printDays('UCT');
printDays('Etc/GMT-9');
printDays('Asia/Tokyo');
printDays('America/New_York'); 
function printDays($timezone) {
echo "timezone:$timezone\n";
date_default_timezone_set($timezone);
$base = strtotime('2011-11-01');
for ($i=4; $i<8; ++$i) {
$date = $base+24*60*60*$i;
$mmdd = strftime('%m/%d  %H:%M', $date);
echo "$i: $mmdd\n";
}
echo "base:$base\n";
echo "now:".strftime('%Y-%m-%d %H:%M:%S')."\n\n";
}
結果はこんな感じ
timezone:UCT
4: 11/05 00:00
5: 11/06 00:00
6: 11/07 00:00
7: 11/08 00:00
base:1320105600
now:2011-11-08 23:50:43 
timezone:Etc/GMT-9
4: 11/05 00:00
5: 11/06 00:00
6: 11/07 00:00
7: 11/08 00:00
base:1320073200
now:2011-11-09 08:50:43 
timezone:Asia/Tokyo
4: 11/05 00:00
5: 11/06 00:00
6: 11/06 23:00
7: 11/07 23:00
base:1320120000
now:2011-11-08 18:50:43 
timezone:America/New_York
4: 11/05 00:00
5: 11/06 00:00
6: 11/06 23:00
7: 11/07 23:00
base:1320120000 
now:2011-11-08 18:50:43
そうそう、 日本は震災でアメリカに..そんなわけない。
使っているサーバのタイムゾーンのデータが壊れているようだ。

ローカルのphp5.3.3の環境では、timezone:Asia/Tokyoの表示は
timezone:Etc/GMT-9と同じ結果になっている。
不思議な動作をするサーバはphp5.3.1。
業務で使うサーバもほぼ同じ環境だから確認しておかなくては。

2011年9月8日木曜日

mySQLの CASE WHENはSQLが長くなる

mySQLの CASE WHENはSQLが長くなるので、短く書く方法を探ってみる。

mySQLのマニュアル比較演算子の冒頭に
「比較演算は、1(TRUE)、0(FALSE)、または NULL の値を返します。」
と書かれている。

なので、下記4つのSQLはすべて同じ結果になる。(はずw。ただし列Cは数値型だとする)

SELECT CASE WHEN `a`=`b` THEN `c` ELSE 0 END FROM `abc`;
SELECT CASE `a` WHEN `b` THEN `c` ELSE 0 END FROM `abc`;
SELECT IF(`a`=`b`,`c`,0) FROM `abc`;
SELECT (`a`=`b`)*`c` FROM `abc`

お好みはどれでしょう。

2011年5月11日水曜日

mySQLについてのメモ

mySQLのINTのMAX値

  • Unsigned で、4G(40億)

mySQLでの正規表現の利用

  • 正規表現は where `name` REGEXP 'a*' のように使用する

mySQLのvarchar、textの比較について

  • インデックスを張る場合255文字以内のキー長を指定する必要がある

他のDBのテーブルを参照するには

  • 同じmySQLサーバ内なら、select * from db_name.table_nameでOK

他のDBのテーブルとJOINできるか

  • 下記のように普通にJOINできる
    SELECT * FROM `mytable` join `dbname`.`atable` A on `mytable`.`id`=A.`id`

synonymはあるか

  • 無い。
    viewを使えば同様のことができる。オーバーヘッドが問題にならないか注意して使う

日付時刻を丸める関数(TRUNCなど)はあるか

  • 日付丸め専用の関数はない。
  • 文字列にフォーマットして丸める例はよく見かける
  • 計算式で丸める例
     時間:DATE(`date`)+INTERVAL HOUR(`date`) HOUR
     日: DATE(`date`)
     週: DATE(`date`)-INTERVAL DAYOFWEEK(`date`)-1 DAY
     月: DATE(`date`)-INTERVAL DAYOFMONTH(`date`)-1 DAY

PostgreSqlにある「関数(式)インデックス」はあるか

  • 無い。→インデックスを使いたい式を展開した列が必要

タイムゾーンはDBもしくは接続毎に再指定可能か
  • 可能
    mysql> set time_zone = '-7:00';
    Query OK, 0 rows affected (0.00 sec)

    mysql> select now();
    +---------------------+
    | now() |
    +---------------------+
    | 2011-05-05 22:21:37 |
    +---------------------+
    1 row in set (0.00 sec)

    mysql> set time_zone='+9:00' ;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select now();
    +---------------------+
    | now() |
    +---------------------+
    | 2011-05-06 14:22:01 |
    +---------------------+
    1 row in set (0.00 sec)

    2011年4月21日木曜日

    Ext4.0ドキュメントの印刷

    Ext4.0のドキュメントページを印刷するブックマークレットを作ってみた。

    今回は隠したインラインフレームに作成して、そのまま印刷するのではなく、
    印刷可能なWindowを表示するようにした。ExtWikiでも試行錯誤したが、プレビュー代わりになりこれが一番便利だった。
    また、ガイドがマニュアルに統合されたので、こちらも印刷しやすいようにしてみる。

    例によってブックマークレット表示サイトで表示してみる。
    ExtJS4.0 ドキュメント印刷(クラス)

    ExtJS4.0 ドキュメント印刷(ガイド)

    もともとβ3の時に作成したが、ファイナルで少し変わっていたので上記に修正した。β3の時のものはこんな感じ
    ExtJS4.0β3 ドキュメント印刷(クラス)

    2011年4月3日日曜日

    画像をphpスクリプトのみで表示する

    画像のパスが問題で、プラグインやライブラリの設計や導入方法が若干複雑になる事がある。
    こんなとき、画像をプログラムに取り込んでしまうと設定自体不要になるケースがある。

    そのやり方をメモしておく
    1.画像をbase64文字列に変換する
    2.変換したbase64文字列をプログラムに組み込む

    1.画像をbase64文字列に変換する
     次のようなプログラムでbase64文字列を作成できる
    img_encode.php
    
    <?php
      //クォート内はターゲットのファイル名
      die(base64_encode(file_get_contents(''));
    ?>
    

    出力は下記のようになる

    R0lGODlhFQATAPcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAMwAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAFQATAAAI/AABCBzoaRQpUVSopFJFaqDDgXny/JH4B8+fUchUOVP1UOCTKhEjjho5KlUqUqpGPXxSoKVLPwmp+CnZ0s/AJypaOqmCJ48fPFScUBGVqmVKgSRaqkhosSWVmi4LOAPgpkTUlkBdPjXqzBmpEjkLqFAhNKFSoUZNkkoqduxSJy7H1iw5ShXbq1iVbi0gCtVXt0up4PnpByYVskz9kKIz1okKnnkGQy0Aw2wBZCUcF6iy2elMP1H9iPLzCcAczqEnF8ha4I/AEnudthTlEtVWTwOtuqS9enZUPQ9LzASacKloVKj8dOoIoESehGXLCgbOXCAmT53++JmYB/fDgAA7

    2.変換した文字列をプログラムに組み込む
     上記で変換して得られたbase64文字列をbase64_decodeしヘッダーとともに出力する。
     1行が長い場合、適当な長さに切り、使うときに文字列として連結する。
    img_decode.php
    
    <?php
      $img = 'R0lGODlhFQATAPcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/M'
     .'M//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8z'
     .'zP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/'
     .'M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xm'
     .'zMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswA'
     .'M8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ'
     .'zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkz'
     .'M5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bM'
     .'zGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZm'
     .'M2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/'
     .'zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZ'
     .'MzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA'
     .'zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM'
     .'MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAz'
     .'zAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAMwAAAP///wAAAAAAAAAAAAAA'
     .'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     .'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     .'AAAAAAAAAAAAAAAAAAAAACwAAAAAFQATAAAI/AABCBzoaRQpUVSopFJFaqDD'
     .'gXny/JH4B8+fUchUOVP1UOCTKhEjjho5KlUqUqpGPXxSoKVLPwmp+CnZ0s/A'
     .'JypaOqmCJ48fPFScUBGVqmVKgSRaqkhosSWVmi4LOAPgpkTUlkBdPjXqzBmp'
     .'EjkLqFAhNKFSoUZNkkoqduxSJy7H1iw5ShXbq1iVbi0gCtVXt0up4PnpByYV'
     .'skz9kKIz1okKnnkGQy0Aw2wBZCUcF6iy2elMP1H9iPLzCcAczqEnF8ha4I/A'
     .'EnudthTlEtVWTwOtuqS9enZUPQ9LzASacKloVKj8dOoIoESehGXLCgbOXCAm'
     .'T53++JmYB/fDgAA7';
      header('Content-Type: image/gif');
      die(base64_decode($img));
    ?>
    

    apacheのmod_rewriteで%2fを変換

    apacheは通常エンコードされたパス分離文字(%2F)が存在するURLを404(Not found)エラーで拒否する。

    通常は問題にならないのだろうが、パラメータをURLに埋め込み、mod_rewiteでphpに処理させるとき、urlやpathのようなパラメータを扱おうとすると顕在化する。

    apacheにこれを許可させるには、apacheのhttpd.confに「AllowEncodedSlashes On」を記述すればよい。

    VartualHostディレクティブ内でもOKだが、.htaccessに記述するとエラーとなる。

    参考
    apacheマニュアル
    PHPプロ!mod_rewriteでの%2F問題

    2011年3月24日木曜日

    ExtJSのStoreにデータダウンロードの機能を追加する PART2

    前回、時間の無い中作った「ExtJSのGridやChartにデータダウンロードの機能を追加する」を利用したアプリケーションで、追加要望が出たので拡張について少し考えてみた。

    前回、拡張の方法としてはGridに特化して、Gridで表示されているカラムやrendererを利用して、見えているものが、タブ区切りでダウンロードできる事を検討した。が、見えているものとは別に列やフォーマットを指定できる方が汎用性が高いことが分かり、その方向で拡張を実施することとした。

    拡張したdownloadメソッド
     /**
      * Download store data
      * @param {Object} opt (optional) Specifies download filename and columns.
      */
     Ext.data.Store.prototype.download = function(opt){
      opt = opt||{};
      var output=[],o=[],
       columns = opt.columns||(function(st){
        var cs=[], i=0;
        st.fields.eachKey(function(k) {cs.push(k);});
        return cs;
       })(this),
       fn = opt.filename||opt.fn||'download.txt';
      for (var i=0,col; col=columns[i]; ++i) {
       if (typeof col === 'string') {
        columns[i] = col = {field: col}
       }
       if (!col.header) col.header = col.field;
       if (!col.format) col.format = function(v,r){return v;};
       o.push(col.header);
      }
      output.push(o.join('\t'));
      this.each(function(rec) {
       var o=[];
       for (var i=0,col; col=columns[i]; ++i) {
        o.push(col.format(rec.get(col.field), rec));
       }
       output.push(o.join('\t'));
      });
      var form = Ext.DomHelper.append(document.body, {
       tag: 'form',
       style: 'display:none',
       action: 'download.php',
       method: 'post',
       cn:[{
        tag:'textarea',
        name:'body',
        html:output.join('\n')
       },{
        tag:'input',
        name:'Content-Disposition',
        value:'attachment;filename='+'"'+fn+'"'
       },{
        tag:'input',
        name:'Content-Type',
        value:'text/tab-separated-values'
       }]
      });
      form.submit();
      document.body.removeChild(form);
     }
    

    呼出しサンプル1
    こちらではパラメータなしでdownloadメソッドを呼び出した例、storeの内容がそのままダウンロードされる

    呼出しサンプル2
    こちらはパラメータとして、ダウンロードファイル名や、列やフォーマットの指定を行ってダウンロードする例

    使用に当たっては「ExtJSのGridやChartにデータダウンロードの機能を追加する」で使用したdownload.phpが必要となります。

    phpの出力をブラウザやプロキシーにキャッシュさせない

    phpのヘッダーのマニュアル内にそのものが載っているが、下記を設定するとかなりキャッシュされにくくなるとのこと。

        header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
        header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // 過去の日付
    

    ちなみに少し以前のマニュアルの例では、もう少しコテコテやっていた。

        // 日付が過去
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        // 常に修正されている
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        // HTTP/1.1
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        // HTTP/1.0
        header("Pragma: no-cache");
    

    2011年3月11日金曜日

    phpで仮名を全角に、英数字と空白を半角に統一する(mb_convert_kana)

    何と1行でできる

    mb_convert_kanaを使用する

    /*「半角カタカナ」を「全角カタカナ」に変換し、
     「全角」英数字を「半角」に変換します。
      オプションの意味
       K: かな半→全
       V: 濁点をマージ
       a: 英数字を半角にする
       s: スペースを半角にする(U+3000 -> U+0020)
     */
    
    $str = mb_convert_kana($str, 'KVas', 'utf-8');
    

    エンコードをEUCもしくはUTF-8とすれば、ついでに英数字の後に続く長音記号をハイフンに変換するのも簡単。

    $str = mb_convert_kana($str, 'KVas', 'utf-8');
    $str = preg_replace('/([a-z0-9])ー/i', '${1}-', $str);
    

    2011年3月9日水曜日

    Illuminations for Devellopers 1.1.5導入

    ExtJSでの開発やデバッグの効率を桁違いに高めてくれるかも知れないツールがFireFoxのアドオンとして提供されている。

    名前は「Illuminations for Devellopers」で今日現在のバージョンは1.1.5となっている。Senchaのブログに載っていたのでインストールしてみた。

    インストールは通常のFirefoxのアドオンと同じ。但し、アドオンFirebugが導入されていることは前提となります。
    「ツール」→「アドオン」メニューから、「アドオンを入手」をクリックし、検索のテキストボックスに「Illuminations」と入力し検索すると
    Illuminations for Developers for Firebug

    作成者: Steven Roussey
    が、見つかるのでこれを「Firefoxに追加」する。インストール完了後にFirefoxを再起動し、Firebugを開くと、一番右「接続」の隣に「Illuminations[Trial]」のタブが増えていることがわかる。赤い大きな「Buy or Login」のボタンが少々いやらしいが、まずは無視。

    早速、ExtJSを使用しているページを見に行くと、ViewPort以下どのようにBorderLayoutがあり、その中にある入れ子のBorderLayoutがTabPanelでありどんなメニューとプロパティを持っているかなど、階層的に確認していくことができる。


    今まで、FireFoxでこれを確認していくことはスクリプトのウォッチの機能でできないことはなかったが、いろいろな情報が頭に入っており、見たいデータを明確にして探さないと見つけることはできなかったし、探したオブジェクトがどんなクラスのオブジェクトかということですら、持っているプロパティやメソッドから推測することしかできなかった。

    このアドオンの利用によりかなりデバッグの敷居が下がるかと思う。
    まだ、全然見切れていないが、導入後気になったのはこのアドオンがトライアルで、製品版があるらしいということ。どんな機能の違いがあるのだろう。

    2011年3月1日火曜日

    ExtJSのGridやChartにデータダウンロードの機能を追加する

    ExtJSのGridやChartにデータダウンロードの機能を追加した際のメモ

    ダウンロードといえば本来サーバ側に実装する機能だが、超短納期で片づけたかったため時間のかからなそうな、javascript側のStoreからダウンロードする方法を検討した。

    作成したサンプルは以下の2つ。いずれもExtJSのサンプルに、ダウンロード機能を呼び出すためのボタンを追加したものとなる。

    Chartのほうはサーバに無いデータを表示する例なので、よりStoreからのダウンロードに意味が出るかと思う。



    ダウンロードはクライアント側だけでは仕組み上実現できないので、ブラウザからPOSTされたデータをそのまま出力する鏡みたいなphpスクリプトを使用することにした。このあたり、javascriptだけでできる方法があるなら、是非コメントでアドバイスいただきたい。

    用意したphpスクリプトdownload.phpは以下のようなものだ。
    本来、3行で実装できるが、ヘッダの自由度を持たせるためと、最低限のセキュリティとして、リファラが同じサーバかだけのチェックを入れている。

    <?php
     if (!isSet($_SERVER['HTTP_REFERER']) 
      || !preg_match('#^http://([^:/]+)#',$_SERVER['HTTP_REFERER'],$mch)
      || $mch[1]!==$_SERVER['SERVER_NAME']
     ) die('Security Error'); // simple security check
     foreach ($_POST as $header=>$val) {
      if ($header==='body') continue;
      header("$header: $val");
     }
     die($_POST['body']);
    ?>
    

    ChartやGridPanelのスクリプトの変更方法は、ツールバーやツールボタンで、下記のStoreに追加したメソッドExt.data.Store::download(fn)を呼び出すというもの、StoreはGridやChartで使用しているものを、引数fnはダウンロードの際のファイル名を指定すればよい。

    Ext.data.Store.prototype.download = function(fn){
        var output=[],o=[],
          fld = this.fields;
        fld.eachKey(function(key){o.push(key)});
        output.push(o.join('\t'));
        this.each(function(rec) {
          var o=[];
          fld.eachKey(function(key){o.push(rec.get(key))});
          output.push(o.join('\t'));
        });
        var form = Ext.DomHelper.append(document.body, {
          tag: 'form',
          style: 'display:none',
          action: 'download.php',
          method: 'post',
          cn:[{
            tag:'textarea',
            name:'body',
            html:output.join('\n')
          },{
            tag:'input',
            name:'Content-Disposition',
            value:'attachment;filename='+'"'+fn+'"'
          },{
            tag:'input',
            name:'Content-Type',
            value:'text/tab-separated-values'
          }]
        });
        form.submit();
        document.body.removeChild(form);
      }
    
    

    今回の方式はStoreにfilterがかかっていれば、表示されているレコードのみDLされる。
    Grid専用にするならば、これを一歩進め、ユーザが画面で操作した列の順番や列の表示/非表示を反映したり、列によってはrendererを指定できるようにすると、ユーザが見たままを手に入れるという意味で完成形になるのではと思う。

    今回の例だと出力したLastUpdateの列のDateの出力形式は、欧米人がみればわかりやすいが、日本人向けでなく、Excelでそのまま処理できない等の不満はある。

    英語版のフォーラムに投稿してみました
    http://www.sencha.com/forum/showthread.php?125611-data-download-function-from-Grid-and-Chart

    2011年2月15日火曜日

    ExtJS Pivot Gridの拡張

    ExtJS3.3のPivotGridは、ヘッダのrendererは無く、セルのrendererのパラメータが値しか受け取れない仕様となっている。今回初めて使用したが、必要に迫られてHackしたのでメモしておく。

    それぞれ下記に数行のコードを修正、追加すればよい
    ・ヘッダのrendererの実装: Ext.grid.PivotAxsisのbuildHeaders()メソッド
       行列のそれぞれ各ディメンジョンごとに指定可能
    ・セルのrendererの拡張: Ext.grid.GridViewのrenderRows()メソッド
       値以外に、行のカウンタ、列のカウンタ、行ヘッダの情報、列ヘッダの情報、
       グリッドの参照を受け取れるようにした

    実装結果はこちら

    2011年2月10日木曜日

    ExtJS pivotのサンプル

    ExtJS pivotのサンプルは下記に紹介されているが、残念なことにリンクが切れている

    Ext JS 3.3 Beta 2 がリリース

    サンプルは別URLに残っているのでリンクをあげておく

    サンプル1
     http://dev.sencha.com/deploy/dev/examples/pivotgrid/simple.html
    サンプル2
     http://dev.sencha.com/deploy/dev/examples/pivotgrid/people.html
    サンプル3
     http://dev.sencha.com/deploy/dev/examples/pivotgrid/countries.html

    ExtJSで折れ線グラフを出したときの線のスタイル

    ExtJSのチャート(Ext.chart)は簡単にグラフを表示できるのだが、
    マニュアルに何が指定できるのか説明がほとんどない。

    デコンパイラでcharts.swfの内部を調べて何ができるのか少し調べてみた。
    ほんとは動的にseriesを増減させるメソッドが無いか調べていたのだが
    それらしいものは無かった。
    代わりにseriesで指定できそうなstyleのプロパティを見つけたのでメモしておく
    ※visibilityでおおっと思ったがpi chart専用だった

    {
     height:350,
     xtype: 'linechart',
     store: store,
     xField: 'date',
     series: [{
      type:'line',
      displayName: 'line1',
      yField: 'val1',
      style: {
       color: 0xFF9999,
       lineSize:2,
       lineAlpha:0.6,
       alpha:0.6
      }
     },{
      type:'line',
      displayName: 'line2',
      yField: 'val2',
      style: {
       color: 0x99FF99,
       lineSize:2,
       lineAlpha:0.6,
       alpha:0.6
      }
     }]
    }
    

    「color」はサンプルのコードにあり、意味も明らかだ
    「lineSize」は線の太さでデフォルトの太めの線は「3」が設定してあるように思える
    「lineAlpha」は線の不透明度でデフォルトは「1(おそらく不透明)」になっている
    「alpha」はマーカの不透明度だと思う

    他に
    images 円グラフのみ
    image 折れ線グラフのマーカのイメージ?
    mode
    colors
    borderColor
    borderAlpha
    fillColor
    fillAlPha
    size
    lineColor 折れ線グラムのみ
    lineAlpha 折れ線グラムのみ
    showAreaFill
    areaFillAlpha
    connectPoints 折れ線グラムのみ
    connectDistinuousPoints
    distinuousDashLength
    showLabels
    hideOverlappingLabels
    font
    visibility 円グラフのみ
    skin 折れ線グラフのみ

    とが、charts.swf > action(3) > Charts > setSeriesStyles() のswitchのcaseラベルにあったが、未調査。

    2011年2月8日火曜日

    ExtJS の Grid の ヘッダーにスタイルを適用する

    ExtJS の Grid の ヘッダーにスタイルを適用にまよったのでメモ

    カラムの指定に
    {header:'field1', css:'color: green;'},
    のように指定すればセルにcssが適用されるが、
    ヘッダに適用する為のコンフィグオプションは無くどうするか迷った。

     http://www.sencha.com/forum/showthread.php?94369-Grid-Header-Style&p=447180
    をみるとidを指定しスタイルを書けばいいらしい。

    スタイルの指定に迷ったが
    {header:'field1', id:'field1'}
    としたならば、
    .x-grid3-hd-field1 {
      color: green;
    }
    

    セルも指定したいなら、
    .x-grid3-hd-field1,.x-grid3-col-field1 {
      color: green;
    }
    
    とかけばいい。

    2011年2月2日水曜日

    json_encode()代替関数

    以前、php5.2未満のサーバのために、json_encode()の代替となる関数を書いた。
    いくつかのプログラムで使用してきたが、3年以上たってバグを発見したので、メモしておく。

    自作json_encode()は原形をほぼとどめていないが、
    phpのマニュアルの中の02-May-2007 01:55のUser Contributed Notesで
    Yi-Ren Chen at NCTU CSIEさんが書いたphp_json_encode()を改造して作ったものだ。

    json作成のロジックで、数字だったらそのまま使い、文字ならばクォートして使用する
    ような部分があるが、ここで数字の判定を
    if (is_numeric($val)) return $val;
    と記述していたが、これに
    $data = array('id'=>'011')
    のような入力を食わせると、
    {"id":011}
    が出力されてしまう。これはブラウザのjavascriptに8進数として処理され
    {id:9}
    と解釈される。

    とりあえず
    if (is_int($val)||is_float($val)) return $val;
    のように記述し、
    {id:"011"}
    と受け取られるようになったことを確認したが、過去作ったシステムが、
    異常動作しないかひやひやものだ。

    Yi-Ren Chenさんのコードを元にjson_encode相当関数を作った世界中の人たちも
    大丈夫だったのかなあと心配してみる。

    修正済みコード
    http://sourceforge.jp/projects/extwiki/svn/view/trunk/plugin/json_encode.php?view=markup&revision=107&root=extwiki

    <?php // $Id: json_encode.php 3 2010-10-02 14:56:27Z mashiki $
    /**
     * json_encode: use this  for early version(<5.2) of php
     *     which support "multibyte" but doesn't support "json_encode".
     * 
     * Modified by mashiki 2008,2009,2011
     * 
     * Original written by Yi-Ren Chen at NCTU CSIE as php_json_encode()
     *  User Contributed Notes 02-May-2007 01:55 in PHP online manual
     */
    function json_encode($val) {
     if (is_int($val)||is_float($val)) return $val;
     if (is_bool($val)) return $val?'true':'false';
     if (is_array($val)) {
      $len = count($val);
      for($ii=0; $ii<$len && isset($val[$ii]); ++$ii) {}
      $temp = array();
      if($len==$ii) {
       for($i=0;$i<$len;$i++) {
        $temp[] = sprintf("%s", json_encode($val[$i]));
       }
       return '['. implode(",",$temp) .']';
      } else {
       foreach($val as $key => $item) {
        $temp[] = sprintf("%s:%s", json_encode($key), json_encode($item));
       }
       return '{'. implode(",", $temp) .'}';
      }
     }
     // else string
     static $from = array('\\',  "\n", "\r", '"');
     static $to   = array('\\\\','\\n','\\r','\\"');
     static $cmap = array(0x80, 0xFFFF, 0, 0xFFFF);
     return '"'. preg_replace_callback(
      '/&#([0-9]+);/',
      create_function('$match','return sprintf("\\u%04x", $match[1]);'),
      mb_encode_numericentity(str_replace($from, $to, $val), $cmap, 'UTF-8')
     ) . '"';
    }
    ?>
    

    2011年1月23日日曜日

    window.printで1ページしか印刷できない問題

     ExtWikiでアクティブパネルの印刷をしようとし、タブの内容をiframeにコピーし、window.printを使って印刷するようにしたところ、最初の1ページしか印刷されないという現象に遭遇した。
    同じ方法をExtJSのAPIドキュメントのタブパネルに試したところ、同じように1ページしか印刷されなかった。
     同様な現象が出たのでこれは一般的な問題かと思い、http://www.sencha.com/forum/showthread.php?121909-SOLVED-How-to-print-the-document-on-tabpanel&p=563722 の質問を投げてみた。その後、iframeを見えるようにして確認をしたところ、1瞬、スクロールバーが見えることに気がついた。ステップ実行で1つ1つスタイルシートを適用したところ原因が判明。

     結局この問題はdoc.cssにある「html,body {overflow:hidden}」の設定が問題だった。ExtWikiも初期にExtJSのAPIドキュメントの表示に似せようと、スタイルの設定を真似た為にこの設定が紛れ込んだようだ。
    現在のExtJSはViewportを使用すると、上記のスタイルが設定されるので、この設定は不要であった。doc.cssに残っているのも歴史的な残骸かと思う。

    作成した、ブックマークレット:ExtJS APIページの印刷
    ※上記は「Hide Inherited Members」と「Extend All Members」を反映しないので使い道はいまいちです

    2011年1月13日木曜日

    utf-8のjsファイルをextdocで処理する

    今まで、utf-8のjsファイルのext-docでの処理が文字化けしてしまうため、一旦、s-jisにしてから、処理を行っていた。
    今日、ext-docのバージョンが上がっていないかと、
    http://www.sencha.com/forum/showthread.php?55214-ext-doc-ExtJS-style-JavaScript-comments-processor/page26

    にアクセスしたところ、javaのオプションで
    java -Dfile.encoding=UTF8 -jar path_to\ext-doc.jar ...
    とすればいいという情報を見つけた。
    試してみると、文字化けもなくうまく動作する。

    utf-8 → s-jis はサクラのマクロでやっていたので、手間としてはそれほど変わらないが、正解がわかったのですっきり。

    2011年1月12日水曜日

    ExtJSの問い合わせをするなら

    ExtJSの問い合わせをする場合、日本語のフォーラム「Ext International」に質問しても最近は誰もレスをつけていない。さびしい限りだ。

    少々面倒だが、見ている人の数が圧倒的に多い英語のサイトに投稿するのが結局速そうだ。
    手順はこんな感じ
     ・問い合わせのテキストを作成し、翻訳サイト( http://www.excite.co.jp/world/english/ )等で翻訳する
     ・ソースのコメントやリテラルの文字列を英語にし、コードタグ([code][/code])でくくる
     ・http://www.sencha.com/forum/forumdisplay.php?40-Ext-Help でフォームにポストする

    Ext-Helpにポストした投稿は

    http://www.sencha.com/forum/search.php?do=finduser&userid=14691&contenttype=vBForum_Post&showposts=1&forumchoice%5B%5D=40


    で調べられる。(14591は自分のユーザID)

    2011年1月7日金曜日

    winscpをバッチ処理で使用する

    こんなこともできるらしい。
    サクラのマクロに取り込めば、いろいろなことが自動化できそう

    下記を1行でコマンドラインに打つ
    "C:\Program Files\WinSCP\winscp.com" /command
       "open ""mashiki@shell.sourceforge.jp"""
       "put ""C:\DLSoft\develop\ext-doc-1.0.131\extwikidoc.zip""
       ""/home/groups/e/ex/extwiki/htdocs/"""
       "exit"

    参考
    http://winscp.net/eng/docs/commandline
    http://winscp.net/eng/docs/script_commands

    実際に作って使っているのがこれ
    ・現在のファイルがextwiki-debug.jsか確認
    ・現在のファイルを保存する
    ・SJISにして名前を付けてextdoc用のフォルダに保存する
    ・ext-docに変換する
    ・生成されたファイル数が大量のためzipに固める
    ・zipをサーバのドキュメントルートにアップロードし解凍する

    // ext-doc.js
    var cmd, oe,
     TARGET = 'extwiki-debug.js',
     DOCDIR = 'C:\\DLSoft\\develop\\ext-doc-1.0.131',
     SAVEAS = 'DOCDIR\\source\\extwiki.js',
     MAKEDOC= 'java -jar DOCDIR\\ext-doc.jar'
       +' -p DOCDIR\\source/extwiki.xml'
       +' -o DOCDIR\\extwikidoc'
       +' -t DOCDIR\\template/ext/template.xml'
       +' -verbose',
     ZIPDOC = '"C:/Program Files/Lhaplus/Lhaplus.exe" /c:zip /o:DOCDIR DOCDIR\\extwikidoc',
     PUTFILE= '"C:/Program Files/WinSCP/winscp.com" /log=log.txt /command'
       +' "open ""mashiki@shell.sourceforge.jp"""'
       +' "cd ""/home/groups/e/ex/extwiki/htdocs/"""'
       +' "put ""DOCDIR\\extwikidoc.zip"""'
       +' "call unzip -o extwikidoc.zip"'
       +' "exit"'
     ws = new ActiveXObject("WScript.Shell"),
     fn = GetFilename().replace(/^.*\\/,'');
    // 現在のファイルがextwiki-debug.jsか確認
    if (fn!==TARGET) {
     ws.Popup('ファイル名"' + fn + '"は処理対象外です', 0, "ファイル名", 0);
    } else {
     // 現在のファイルを保存する
     FileSave();
    
     // SJISにして名前を付けて保存する
     FileSaveAs(resolveVars(SAVEAS),0,1); // SJIS, CRLF
    
     // ext-docに変換する
     oe = ws.Run(
      resolveVars(MAKEDOC),
      1,   // ウィンドウをアクティブにして表示する。
      true // 終了するまでスクリプトの実行を停止
     );
    
     // zipに固める
     oe = ws.Run(
      resolveVars(ZIPDOC),
      1,   // ウィンドウをアクティブにして表示する。
      true // 終了するまでスクリプトの実行を停止
     );
    
     // zipをサーバにアップロードし解凍する
     oe = ws.Run(
      resolveVars(PUTFILE),
      1,   // ウィンドウをアクティブにして表示する。
      true // 終了するまでスクリプトの実行を停止
     );
    
    }
    
    function resolveVars(path) {
     return path.replace(/DOCDIR/g, DOCDIR);
    }
    

    2011年1月3日月曜日

    SourceForgeのDiff画面でサイドバーを非表示にするブックマークレット

    SourceForgeのDiff画面は右側のサイドバーが大きく、せっかくソースを並べて表示できても、まったくもって見やすいとは言えない状態です。

    下記で紹介しているブックマークレットはサイドバーを一時的に非表示にします。

    SourceForgeの画面でサイドバーを一時的に非表示にする

    ページ移動もしくはリロードすれば元に戻ります。
    SourceForgeの収入源である広告の表示やクリックできるチャンスを損なうものではないと思っていますが、なにかお気づきの点があればご指摘いただければ。