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