APC と jQuery を利用してファイルアップロードの進行状況を表示する

  ネタ的には特別新しいものではないですが、気になっていたので試してみました。
  PHP 5.2.0 以降、APC 3.0.13 が必要です。

1. APC の設定

  普通に APC をインストールして、以下のように apc.rfc1867 を有効にするだけです。

apc.rfc1867 = On

2. アップロードフォームの準備

  普通のアップロードフォームです。
  ただし、"APC_UPLOAD_PROGRESS" という name 属性を持った hidden フィールドを用意します。値は、何でもいいのですが、ここでは "progress_key" という風にしています (本当はランダムにするほうがいいと思います)。

<form id="upload" action="upload.php" method="post" enctype="multipart/form-data">
<p>
<input type="hidden" name="APC_UPLOAD_PROGRESS" value="progress_key" />
<input type="file" name="file" />
<input id="submit_button" type="submit" value="アップロード" />
</p>
</form>

3. アップロードファイルの処理

  上記アップロードフォームの upload.php ですが、これは通常のファイルアップロード時の処理を書いてください。
  今回はテストなので何もしませ

<?php
// 通常のファイルアップロードの処理
?>

4. アップロードの進行状況を返す処理

  次のファイルを progress.php とします。

<?php
// progress.php
header('Content-type: application/json; charset=UTF-8');
$status = apc_fetch('upload_progress_key');
echo json_encode($status);
exit;
?>


  2 で指定した "APC_UPLOAD_PROGRESS" の値の先頭に upload_ をつけたものを apc_fetch() の引数に指定します。upload_ は設定で変更することが出来ます (apc.rfc1867_prefix)。
  apc_fetch() で取得できる値は次のようになります。

total          アップロードされるファイルのサイズ                              
current        現時点までに受信したファイルのサイズ                            
rate           アップロード速度 (byte/second)           アップロード完了時のみ
filename       ファイル名                                                      
name           <input type="file" /> の name 属性                              
temp_filename 一時ファイル名                           アップロード完了時のみ
cancel_upload アップロードがキャンセルされたかどうか   アップロード完了時のみ
done           アップロードが完了したかどうか                                  
start_time     アップロード開始日時の UNIX TIME                                
  • cancel_upload: 0 = キャンセルされていない、1 = キャンセルされた
  • done: 0 = 未完了、1 = 完了

  これらの値を JSON で出力しています。

5. jQuery を使って進行状況を取得する

  jQuery Form Plugin を使って、フォームを POST し、1 秒毎に getJSON で進行状況を取得します。
  また、getJSON でリクエストに現在日時を渡しているのは、ブラウザのキャッシュを利用しないようにするためです。

var timer = null;

var progress = function() {
    // progress.php を呼び出して進行状況を取得する
    $.getJSON('progress.php', { 'd': new Date().getTime() }, function(json) {
        // 進行状況を % で表示する
        $('#status').html(parseInt(json.current / json.total * 100) + '%');
    });
};

$(function() {
    $('#upload').submit(function() {
        timer = setInterval('progress()', 1000);
        // フォームを POST する
        $(this).ajaxSubmit(function() {
            clearInterval(timer);
            progress();
        });
        return false;
    });
});

6. デモ

  本当は、デモを用意したかったのですが、ここのレンタルサーバでは APC が使えないようでしたのでアップロード状況をキャプッチャしました。
  基本的には、上記の処理を行なっているだけですが、進行状況の表示の部分は Progress Bar Plugin を利用して、プログレスバーで表示しています。



  このデモで使用したファイル一式を以下に置いておきます。

参考


- PHP V5.2 の新機能、第 5 回: ファイル・アップロードの進行状況を追跡する方法
  http://www.ibm.com/developerworks/jp/opensource/library/os-php-v525/

- upload meter for PHP with APC and Json
  http://progphp.com/progress.phps

- PHP: APC 関数 - Manual
  http://php.net/apc

- jQuery Form Plugin
  http://malsup.com/jquery/form/

- Progress Bar Plugin
  http://digitalbush.com/projects/progress-bar-plugin

IE でどうにかして color:inherit を有効にする

  IE (6 も 7) では、CSS の color: inherit に対応していないので、以下のような場合に他のブラウザと異なる表示になります。

* {
  color: #000;
}
 
p {
  color: #f00;
}
 
span {
  color: inherit;
}

<p><span>ここは color: inherit を指定しています。</span></p>


- Internet Explorer 6.0
  color: inherit of IE

- Firefox 2.0.0.12
  color: inherit of Firefox

  見て分かるとおりに、IE の場合は、ユニバーサルセレクタで指定した #000 が有効になっています。
  要は、CSS 2 に準拠していないということなのですが、これをどうにかして他のブラウザのように #f00 で表示してやろうというのが趣旨です。

  結論から言うと JavaScript での処理になるのですが、はじめに以下のようなスクリプトを書いてみました。

window.onload = function() {
  if (document.all) {
    var e = document.getElementsByTagName('span');
    for (var i = 0, l = e.length; i < l; ++i) {
      e[i].style.color = e[i].parentNode.currentStyle.color;
    }
  }
};


  要は、親要素の color を適用していくという感じです。もちろん、これでうまくいきます。

  jQuery を利用している場合は、以下のような感じで、

$(function() {
  if ($.browser.msie) {
    $('span').each(function() {
      $(this).css('color', $(this).parent().css('color'));
    });
  }
});


  prototype.js を利用している場合は、以下のような感じです。

Event.observe(window, 'load', function() {
  if (Prototype.Browser.IE) {
    $$('span').each(function(e) {
      e.style.color = e.parentNode.currentStyle.color;
    });
  }
});


  さて、上記のスクリプトを見て分かるとおりに IE でしかこの処理は行ないません。
  となると、IE の CSS 拡張 expression が使えそうです。

span {
  color: inherit; /* NOT IE */
  color: expression(this.parentNode.currentStyle.color); /* IE */
}


  これだとシンプルに書くことが出来ます。
  ただし、expression も問題があって、何かしらのイベントが発生するたびに評価されてしまいます。
  例えば、mouseover だったり riseze だったり。
  あともちろん、validator には通りません。

  結論としては、上記の方法を時と場合によって使い分けるのが良いと思います。

# IE が color: inherit をサポートしてくれるのが一番いいんですけどね……
# IE 8 ではどうなっているんでしょう?

- hxxk.jp - IE 7 と color: inherit
  http://hxxk.jp/2006/11/19/2347

長い URL を折り返して表示する jQuery プラグインを作りました

  主に Firefox での使用を意識しているのですが、Firefox では長い URL は折り返さない仕様なのでページのレイアウトが崩れてしまうことがあります (Firefox 3 では折り返す仕様になるみたいです)。

  このため MR Tech Link Wrapper というアドオンや、url_breaker+ という Greasemonky スクリプトがあります。

  それらを参考にして、長い URL を折り返して表示する jQuery プラグイン Link Wrapper を作ってみました。

- jquery.linkwrapper.js のデモ
  http://pocari.org/demo/jquery.linkwrapper/

  jquery.linkwrapper.js

  このプラグインは、MR Tech Link Wrapper や url_breaker+ と同じように、<wbr> という非標準のタグを挿入しています。また、Opera は <wbr> に対応していないようなので、&#8203; (ZERO WIDTH SPACE: ゼロ幅スペース) を挿入しています。

# ですので、Opera では URL の文字をコピーすると h t t p : / / w w w のようにスペースが入ってしまいます。

  このプラグインでは、<wbr> の挿入位置ですが、デフォルトでは 1 文字毎にしています。
  1 文字毎はやりすぎな感じもするのですが、まあ綺麗に折り返すために、取り合えずこのようにしてみました。

  オプション (pattern) に <wbr> を挿入する正規表現を指定することで、折り返しの位置を調整することができます。

- 使い方

<script type="text/javascript" src="jquery-1.2.2.pack.js"></script>
<script type="text/javascript" src="jquery.linkwrapper-1.0.3.js"></script>
<script type="text/javascript">
// <![CDATA[
$(function() {
  // オプションを指定しない場合
  $('a.link1').linkwrapper();
  // オプションを指定する場合
  $('a.link2').linkwrapper({ pattern: '(&|\\?)' });
});
// ]]>
</script>
</head>
<body>
<p><a href="http://..." class="link1">http://...</a></p>
<p><a href="http://..." class="link2">http://...</a></p>


  対応しているブラウザですが、IE 6.0、IE 7.0、Firefox 2.0.0.11、Safari 3.0.4、Netscape 7.1、Opera 9.25 で確認したところ問題ないようです。

  以下にソースを載せておきます。
  ライセンスは、jQuery と同様に MIT と GPL のデュアルライセンスにしました。

(function() {
  jQuery.fn.linkwrapper = function(config) {
    config = jQuery.extend({
      pattern: '(.)'
    }, config);
    var pattern = new RegExp(config.pattern, 'g');
    var tag = jQuery.browser.opera ? '&#8203;' : '<wbr />';
    return this.each(function() {
      jQuery(this).html(jQuery(this).text().replace(pattern, '$1' + tag));
    });
  };
})(jQuery);


- ダウンロード
  jquery.linkwrapper-1.0.3.js

  なお、jQuery のプラグインの作り方は、以下のエントリが大変参考になりました。

- ref.: jQuery のプラグインを作成する : ブログの新着記事を表示:Goodpic
  http://www.goodpic.com/mt/archives2/2007/11/jquery.html

- ref.: The wbr tag
  http://www.quirksmode.org/oddsandends/wbr.html

- 追記 (2008-02-05)
  メソッドチェーンができないことに気づいたので、jquery.linkwrapper-1.0.2 をリリースしました。
  これで、以下のようにできるようになります。

$('a.link1').linkwrapper().css('color', '#f00');


- 追記 (2008-03-21)
  jQuery.noConflict(); の時に動作しないのを修正して、jquery.linkwrapper-1.0.3 をリリースしました。