[ カテゴリー » 開発日誌 ]

メソッドチェーンで遅延処理をしたい

category-icon

 こんばんは、martinです。タイトル通りなんですが、要は以下のようなことを簡単に実装したい。

foo.await(2000).say("Hello, ").await(1500).say("world !");

 まぁfooという関数オブジェクトがあるとして、2秒後に「Hello, 」を出力して、そこで小休止、で1.5秒後に「world !」を出力すると。それをメソッドチェーンに拘ってやりたい。簡単な例を挙げてみます(うまく行かないサンプルです)。

var foo = function(){}; // 関数オブジェクトを作成

foo.prototype = { // プロトタイプを設定
 say : function(s){
  alert(s);
  return this;
 },
 await : function(ms){ // ここで遅延処理をゴニョゴニョしたい
  // var _this = this;
  // setTimeout(function(){
  // return _this; # まぁ、これはダメだけど、こんな感じでやれたらいい
  // }, ms);
   return this;
  }
}

var bar = new foo();
bar.await(2000).say("Hello"); // すぐに「Hello」が出力される

 メソッドチェーンを実装するとなると、return this;をかまして、用意した関数オブジェクト自身を返すようにする必要があります。上のawait関数内で、setTimeoutを用いて遅延処理をしようと思いましたが、どうもうまい方法が思いつかず、とあるサイトで質問してみましたLink 。すると、数分後(!)に返答があって、「それ、jQueryで出来るよっ!」と(--)(そのコメントは今は消されたようなんですが) まぁ確かにそうなんですが(なのであえて.delayという単語を選んだ)何かすごい技があって、それで解決!みたいなことを期待していたわけです(jQueryを使わずに)。

 2つ目に付いたコメントで、簡単に済ませる方法はなくて、何か別のキューシステム(待ち行列システム?)を用意する必要があるよ、と。やっぱりそうか。で、沢山ヒントを頂いたんで、自分なりに作ってみました。次のようなキューシステムを用意しました。

Queue = {
 entries : [], // 関数を登録する配列を用意
 inprocess : null,
 enqueue : function(entry){ // 関数を登録する
  Queue.entries.push(entry);
 },
 flush : function(){ // 配列に登録された関数群を一気に実行
  if(Queue.inprocess) return; // 「待ち中」なら実行しない
  while (Queue.entries.length){
   var entry = Queue.entries.shift(); // 配列の頭から取り出す(つまり登録順)
   if(entry.toString().indexOf('await:') !== -1){ // 「待ち」のマークを見つけたら
    var ms = Number(entry.split(':')[1]);
    Queue.inprocess = setTimeout(function(){
     Queue.inprocess = null;
     Queue.flush(); // 指定時間後に処理を再開
    }, ms);
    return;
   }
   entry(); // 取り出された関数を実行
  }
 }
}
 こいつを最初のサンプルに適用すると以下のような感じになります。
var foo = function(){};

foo.prototype = {
 say : function(s){
  Queue.enqueue(function(){ // 配列に関数を登録
   alert(s);
  });
  Queue.flush();
  return this;
 },
 await : function(ms){
  Queue.enqueue('await:' + ms); // 配列に「await:2000」みたいなマークを登録
  return this;
 }
}

 自分のスキルではこの程度です(--) キューシステムに頼らずに自己完結できる目から鱗な手法がないですかねぇ。

とりあえず動作サンプルを以下に。ボタンを押すと、

var bar = new foo();
 bar.await(1500).say('Hello, ').await(2000).say('world!');
ってのが実行されます。ここでは、ボタンを押して1.5秒後に「Hello, 」、更に2秒後に「world !」とボタンの右側に表示されます。
 何か、「こうしたらいいよ」とかのアドバイスあればよろしくお願いします。

 


— posted by martin at 08:29 am   commentComment [0]  pingTrackBack [1]

IE8でのHTML5要素有効化あれこれ

category-icon

 もう7月も終わりですね、martinです。相変わらず時の流れは速いもので。

 このサイトのブログの基本テーマ(スキン)「Basic」では、試験的にHTML5を導入しています。HTML5では新しい要素がいくつか追加Link されていますが、IE9未満のブラウザでは、articleheader, navなどのクールで新しい要素に対するスタイルシート指定が効かない、という事が昔から知られています。これに対するアプローチとして、 document.createElementを使うとスタイルシートでの指定が可能になるよ、というのがよく知られています。初出は Sjoerd VisscherLink さんあたりでしょうかねぇ。
document.createElement(”article”); // 未知の要素articleに対してIEでもCSS指定が可能になる

 このdocument.createElementを使うテクニックは、いろんなところで見かけることができて、有名どころでは、Remy sharp氏のhtml5.jsLink あたりですね。

 もっとも、このテクニックを使っただけでは、IEでHTML5化を効かせたページを印刷したときまでは反映されないので、これに対しては、IE Print Protector Link がよく知られていると思います(個人的には、印刷のサポートまではあまり興味がありませんが)。

 で、このcreateElementを使うテクニックは、ベタに書けば以下のような感じになります。

// ここでは簡潔化のため、以下の6個の新要素に絞ってます。
<!--[if lt IE 9]>
var html5_elements = ["header", "nav", "article", "section", "aside", "footer"]; 
for (var i = 0, len = html5_elements.length; i < len; i++){
 document.createElement(html5_elements[i]); // 各要素に適用
}
<![endif]-->
 どこで最初に見かけたのか失念しましたが、これを一行(ワンライナー)で簡潔に済ませたスクリプトを見たときはいたく感心しました。Dean Edwards氏のブログLink もこうなっています。
"header,nav,article,section,aside,footer".replace(/¥w+/g, function(a){document.createElement(a)});
 上のポイントは、replaceメソッドの引数に関数を指定できて、その関数内では、正規表現にマッチした要素を適宜適用していく点です。ループみたいなことを勝手にやってくれる点ですね。おそらくこれ以上短くは書けないのではないかと思うのですが、息抜きに自分なりにいくつか考えてみました。
"header,nav,article,section,aside,footer".split(',').sort(function(a){return document.createElement(a)*1});
 これも、似たような発想からですね。自動でなんかやってくれるという。
with("header,nav,article,section,aside,footer".split(',')))while(length)document.createElement(pop());
 ここではwith構文を使ってみました。これもなかなかシンプルです。同じくwithと、Enumeratorを使って、
with(new Enumerator("header,nav,article,section,aside,footer".split(',')))for(;!atEnd();moveNext())document.createElement(item());
 これは、ちょっと長いし、Enumerator ObjectLink はマイナーですかね。

 短さ命で、グローバル変数の汚染なんて気にしないぜっ、という向きには以下のようなものもありかと思います。

s="header,nav,article,section,aside,footer".split(',');while(s[0])document.createElement(s.pop());

 for文関連では、

for(i in s="header,nav,article,section,aside,footer".split(','))document.createElement(s[i]);
とか
for(i=0;n="header,nav,article,section,aside,footer".split(',')[i++];document.createElement(n));
とかですかねぇ。

 ちなみに、配列を作るのに、文字列にsplit()をかませるというのは、よく見かける手法です。ここでの、6個ぐらいの要素数ではあまり差は出ませんが、要素数が増えてくると、逐一ダブルクォート(or シングルクォート)で括っていくやり方と大きく差が付いてきます。

var html5_elements = ["header", "nav", "article", "section", "aside", "footer"]; // これより
var html5_elements = "header,nav,article,section,aside,footer".split(','); // こっちが記述が短い 

 正規表現から配列も出来ますね。

var html5_elements = "header,nav,article,section,aside,footer".match(/¥w+/g);

 以上、トリビアルなエントリーでした

   


— posted by martin at 12:18 pm   commentComment [0]  pingTrackBack [1]

HTML5 FileAPIの不満な点

category-icon

 こんにちは、martinです。なかなかまとまった時間が取れないので、ppBlogの最新版をリリースできずにいます(--)

 最新版のバージョンとしては、v1.9.0を予定しています。新機能としては、コメント認証機能だとか、HTML5を意識したppBlogエンジンでしょうか(ちなみにテーマBasicのソースはHTML5仕様ですよ)。

 で、このHTML5なんですが。過去のエントリーでいくつかあるように、最新版では、記事投稿の際に複数ファイルアップロードが可能になっています。HTML5のFileAPILink を積極的に採り入れたものにしようか、それとも「レガシーな」もので実現させるか迷いましたが、とりあえず「レガシー」なバージョンで行こうと思います。理由はいくつかありますが、一番の理由は、どうもHTML5のFileAPIの使い勝手が悪い点です。

 ブログでの写真投稿において、一度に複数枚の写真を選択してアップロードできる点は、HTML5を使う魅力のひとつですが、この「複数枚のファイルを選択」というのが、どうも洗練されていない印象です。例えば、アップする写真を複数枚決めてそれをアップロードする場合を考えてみます。頭の中では、大まかに写真をのせる順番が決まっていて、ファイル選択画面から、コントールキー(CTRL)を押しながら、順序良く写真をいくつか選んだとします。最新のブラウザなら以下のように、multipleを指定するだけで、この複数枚選択という動作が可能になります。

<input type="file" name="src[]" multiple />

でも、残念なことに、この選んだ順序というのは、FileListLink には反映されないのです。否応なしに、ファイル選択画面上で表示されている通りの順序になるようです。何か、選んだ順序を紐付けするような属性が欲しくなります。

 もうひとつ。選んだ複数枚のファイルは、FileList配列に収められて、あたかも配列のようにDOM操作が可能ですが、この配列は、readonly、つまり読み取り専用Link のようです。たとえば、ちょっと余分に画像を選んじゃったよ、という場合に、その余分なファイルだけをリストから除きたいというケースは、間違いなく出てくると思いますが、そういう操作は出来ないようです。再度、すべてのファイルを選び直す必要があります。これは、ユーザーには使い勝手が悪いですね。

 というわけで、上記の理由により、ppBlogではW3C FileAPIの積極的な採用は見送りました。でも、HTML5に頼らずとも、一度に複数枚のファイルのアップロードは可能です。Stickmanさんが、2005年に、すでにそういうギミックを見付けていましたLink 。最初見たときは、目から鱗でした。やってることはすごくシンプルなんですが、まさにコロンブスの卵ですね。で、彼のスクリプトは、ppBlogにはちょっと冗長でしたので、参考にしつつ、ppBlog仕様にしました。実際のデモを見てみましょう。尚、デモでは「アップロード」ボタンは、文字通りただのボタンなので、実際にはファイルはアップロードされませんが、雰囲気は十分に伝わると思います。

レガシーな複数ファイルアップロードのデモ →http://p2b.jp/demo/EasyFileUpload.htmlLink

 また、Firefox最新版など、File APIに十分対応しているブラウザでは、選んだ画像をサムネイル表示するようにしています。Firefoxだと以下のようなスクリーンショットです。Firefox以外では、普通に選んだ画像のファイル名が選んだ順にリスト表示されます。

MFU-shot
Firefox3.6でのスクリーンショットです。

この部分のスクリプトは以下の感じ。

 if(window.File && window.FileList && window.FileReader){ // FileAPIに対応しているなら
  var file = el.files[0];
  if(file.type.match(/image.*/)){
   var reader = new FileReader(); // FileReader オブジェクト!
   reader.onload = function(){
    var span = d.createElement("span");
    span.innerHTML = '<img class="thumb" src="' + this.result + '" alt="preview" /> (' + Math.round(file.size / 1000) + ' KB)';
    li.insertBefore(span, li.lastChild);
   };
   reader.readAsDataURL(file); // 画像データの読み込み
  }
 }

 レガシーなインターフェイスですが、任意のリストを削除出来ますし、HTML5仕様より却って高機能(=使い勝手が良い)な気がします。

 ちなみに、デモのソースを見ていただけると分かりますが、INPUT[type=file]要素に、レガシーにonchangeイベントハンドラを仕込んでいます。document.addEventListerで監視しても良いのですが、IEでは、onchangeイベントがバブルしないようで:(

 余談ですが、「目から鱗」の語源は、新約聖書の「使徒行伝(しとぎょうでん)」中のエピソード、見えなくなっていたパウロLink (サウロ)の目からうろこのようなもの(コンタクトレンズ?)が外れて再度見えるようになったという「パウロの回心」からですね。「豚に真珠」と同様、日本とか中国由来と思いきや、新約聖書からの諺です。

   


— posted by martin at 09:28 pm   commentComment [4]  pingTrackBack [0]

短縮URLの元を知る その2

category-icon

 こんにちは。前回のエントリーのコメントで「生ログ」の「リンク元」にも「expand」アイコンをとの要望がありましたので、そうしました。ついでに、短縮URLのサービスホストを判定するロジックの精度を少しばかり高めました。単に、文字列の長さのみで判定すると、p2b(笑)だとか、twitterとかも短縮URLサイトと判定してしまうので。

 具体的には、ホワイトリストのホスト名を配列で指定するようにして、そうでなければ「expand」アイコンを付けるという感じです。現状、以下のようになっていますが、適宜追加しても良いでしょう。

$not_shortening_hosts = array('twitter', 'www', 'p2b');

 これは、view.phpの78行目で指定しています。

 ブラックリスト方式でやろうとすると、どうやら100以上のサイトLink を指定する必要がありそうで、ちょっとシャレになりません。

 最新版を添付しておきます。

添付ファイル: view.phpattachedIcon 

— posted by martin at 01:40 am   commentComment [2]  pingTrackBack [0]

短縮URLの元を知る

category-icon

 おはようございます、martinです。WC、初戦勝ちましたねぇ。正直期待していなかっただけに喜びも倍増といったところ。決勝トーナメント進出!なんて日が来るのでしょうか。

 さて、今回は前回のアクセス解析の続きみたいなものです。前回のエントリーで、短縮URLの元を知るサイトとして、http://knowurl.com/Link を紹介しましたが、他にもいくつかありますね。LongURLLink なんかもそうです。LongURLは、Firefoxの拡張版Link もあるようですね。APILink も公開されているようでjQueryのプラグインとかもあるようです。APIがあるんで、ppBlogでも使える様なJavaScriptを書いても良いですが、仕組みは結構単純なので、外部のAPIに頼らずともppBlogのみで完結できそうです。

 まずは、短縮URLがどのような仕組みになっているのか知る必要があります。短縮URLを提供しているサイトはいくつもあります(現時点で動いているのは bit.ly、j.mp、cli.gs、Short.ie、Idek.net、is.gd、sn.im、u.nuあたり)が、これら殆どのサイトでやっていることはリダイレクトです。PHPを使えば、その辺りの情報は簡単に取得出来ます。例えば、http://bit.ly/aKhZ1GLink は、http://p2b.jp/201006-get-html5-fileuploading-moduleというリンクの短縮形ですが、PHP経由でアクセスしてみると、リダイレクト先の情報などが入手出来ます。PHP5であれば、get_headers()Link 関数が使えます。便利ですね。この関数を使って以下を実行すると、

<?php
$url = 'http://bit.ly/aKhZ1G';
print_r(get_headers($url, 1));
?>
大まかには、以下のようになります。
Array(
 [0] => HTTP/1.1 301 Moved  /* Permanentlyと続くこともある */
 [Location] => http://p2b.jp/201006-get-html5-fileuploading-module /* リダイレクト先がある! */
 [MIME-Version] => 1.0
 [Content-Length] => 313
 [X-Powered-By] => PHP/5.1.6
)
[Location]の項目に短縮URLの元リンクが収められていますね。なので、配列からこれを取り出せばOKです。じゃPHP5ではないサーバーではどうするか。fsockopen()関数Link を使います。以下のような記述で行けるでしょう。
$url = 'http://bit.ly/aKhZ1G';
$_url = parse_url($url);
$port = isset($_url['port']) ? $_url['port'] : 80;
$host = isset($_url['host']) ? $_url['host'] : '';
$path = (isset($_url['path']) ? $_url['path'] : '/').(isset($_url['query']) ? '?'.$_url['query'] : '');
$request  = "GET ".$path." HTTP/1.1¥r¥n";
$request .= "Host: ".$host."¥r¥n";
$request .= "Connection: Close¥r¥n¥r¥n";

if($fp = fsockopen($host, $port, $errno, $errstr, $timeout=3)){
  stream_set_timeout($fp, 3);
  $ret = '';
  fputs($fp, $request);
  do {
   $ret .= fgets ($fp, 4096);
  } while (strpos($ret, "¥r¥n¥r¥n") === FALSE); // ヘッダー部分で十分
  fclose($fp);
}

これを実行すると、以下のような感じになります。

HTTP/1.1 301 Moved
Server: xxxx/0.7.42
Date: Tue, 15 Jun 2010 02:46:59 GMT
Content-Type: text/html; charset=utf-8
Connection: close
Set-Cookie:xxxx
Location: http://p2b.jp/201006-get-html5-fileuploading-module /* これが欲しい */
MIME-Version: 1.0
Content-Length: 313

これから、Location:の部分を取り出す正規表現は、以下のような感じでしょうか。

$redirectURL = preg_replace('{^.+?Location:¥s([^¥n]+?)¥n.+$}s', '$1', $ret);
これで、$redirectURLにhttp://p2b.jp/201006-get-html5-fileuploading-moduleという文字列が入ることになります。

 以上のことをやると、リダイレクト先が分かるのでppBlogのアクセス解析に組み込んでみました。「リンク元」の項目で、短縮URLっぽいものには、下のショットにあるように「expand」というアイコンみたいなやつが付くようになります。

shot1
短縮URLなので一体どこへのリンクなのか分からない

 そしてこれをクリックすると、Ajax経由で情報をゲットして以下のような表示に変わります。

shot2
その場でリダイレクト先のアドレスが表示される

 リダイレクト先があれば、これがその場で表示されます。これはリンクになっていますが、そのサイトへのリンクではなくて、グーグルでそのリンクを調べるようになっています。それで怪しげなサイトであればブラウザでアクセスするのを避けることが出来るわけです。

 ちなみに、Ajax部分は、以下のような関数で実現しています。思ったよりシンプルに出来ました:)

  var expand = function(url){
   var target = o(oParts.evt.target);
   var loader = oParts.create('IMG').src('../Images/loader.gif'); /* ローディングアニメーション */
   target.addAfter(loader);
   oParts.server.get('view.php?expand=' + url, function(ret){ /* これがAjax部分 */
    loader.away();
    if(ret == url){
     target.css('color:gray; cursor: default').html('redirect 0').title('リダイレクトはありません').on('click', function(){});
    } else {
     target.css('color:crimson; border-color: green').html('Redirect: <span onclick="googleIt(¥''+ret+'¥')" title="グーグルで検索">' + ret + '</span>');
    }
   });
  }

 一応、これを実現するためのview.php添付しておきますstatディレクトリの既存のやつと置き換えればOKかと思います。前回添付したものの上位版になります。

 今週末にppBlogの最新版配布を予定していますが、これはそのバージョンに組み込み予定です。

 


— posted by martin at 12:24 pm   commentComment [2]  pingTrackBack [0]

T: Y: ALL: Online:
Created in 0.0061 sec.
prev
2024.3
next
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31