A Django site.
12月 15, 2007
» Dragオブジェクトがscrollなdivを乗り越える方法1

今回は、技術的なネタを書いてみようと思います。二回に分けて書こうと思います。今日はですます調な気分です。

ネタは、3ヶ月程前にはまった内容にしてみようと思います。前々から忘れてしまう前にポストしないとなぁと思いながら、先延ばしにしていました。問題としては、以下のように当時は書いておりますね。

draggableな要素がoverflowのhidden, scroll, auto(たぶん)のときには、そのoverflowを指定してある要素の外では見えなくなっちゃう。

また、この問題はscript.aculo.usの開発者も知っているのだけど、なかなか対応が面倒なもののようです。こんな感じに言っていますから。

I discussed this with Thomas some time ago, and the problem is that the element we move (be it in ghosting mode or not) usually must remain at the same DOM level because otherwise, styles might get lost. I'm working on a solution right now, too, it will involve a new option you can use to assign a class name to the element while it's being dragged. If this class name is supplied, the element will be attached to the body. Let's see if this works, I'll provide the patch as soon as it is ready

一応、closedとなっており、 Effects Treasure Chestにその解決方法が上がっているのですが、以下の質問があるように、私もこのソースでは、実際に動いているところを見たことがありません。動作は見ていないのですが、ソースを軽くみたら、がんばったら動きそうな感じがしますけどね。そして、この方法は、実際のライブラリには組み込まれていません。

Could you show an example of how to use this effect?

Edit 01/08/2007 by Greg Hinch – added a line to turn this.dragging on and off when calling initDrag() and finishDrag(). This was to fix an issue where if a draggable was clicked but not dragged the duplicate element would not be removed from the page.

さて、この問題は、scrollなdivの要素の下にDragオブジェクトがあったら、そのdivを乗り越えることができないというものです。これはライブラリの問題ではなく、ブラウザの実装の話です。そういう仕様なのですね。まぁ、IE6では大丈夫だけど、IE7、Mozillaではダメというのがなんともやるせないですが。

実際どういう状態になるか、簡単にスクリプトを書いてみました。
下のページを見てください。

Drag object over scroll div(Not Working!)

右ペインにあるtakoとかikaといったdiv要素はドラッグが可能です。しかし、右ペインから左ペインにドラッグしようとすると見えなくなってしまいます。この状態をなんとかしたいのです。さらに不思議なことに、見えなくなっても、実はドロップすることはできます。再び書きますが、おそらくIE6ではちゃんと左ペインにDragオブジェクト持っていってもちゃんと見えると思います。確認はしていませんが、3ヶ月前に調査したときは、IE6では想定した動作をしていました。

JavaScriptの方では、動かないバージョンでは以下のようになっています。

  1. Event.observe(window, 'load', function() {
  2.   $A($('scroll-b').getElementsByClassName('drag-obj')).each(function(obj) {
  3.     new Draggable(obj, { revert: true, scroll: 'scroll-b' });
  4.   });
  5.   Droppables.add('drop-container', {
  6.     hoverclass: 'dragdrop-in',
  7.     onDrop: function(obj, target) { target.innerHTML += obj.innerHTML+"<br />"; }
  8.   });
  9. });

大したことはしていません。class属性の値にdrag-objを持つ要素をドラッグ可能にしています。また、id属性の値にdrop-containerを持つ要素をドロップ可能にしており、ドロップした際に、その要素のinnerHTMLにドラッグ要素のテキストを書き足します。

そして、CSSの方で重要な場所が以下のようになっています。

  1. #scroll-a {
  2.   float: left;
  3.   width: 70%;
  4.   height: 100%;
  5.   overflow-y: scroll;
  6. }
  7.  
  8. #scroll-b {
  9.   height: 100%;
  10.   overflow-y-: scroll;
  11. }

別にscroll-aはoverflowがscrollでなくてもいいのですが。。。あと、overflow-yって、IEだけだと思っていましたが、手元のfirefoxでは動いたので、とりあえずこれでいきます。

今回はここまでです。動く方のソースは実はまだ頭の中で、ソースには書き出してはいません。どうやるか、ということですが、Dragオブジェクトをscroll-bの下ではなく、body直下に置く、もしくは、scroll指定していない場所に置くという方法を採用する予定です。

来週中に書くことを目指します。

そういえば、2ヶ月振りにscript.aculo.usを使いました。いつの間にか1.8.0が出ていましたね。prototype.jsのバージョン1.6ともcompatibleでうれしい限りです。

11月 17, 2007
» for文書きたくない。

タイトルにYUIとか書くと全く関係ないところが、キーワードを拾って、リンクを張ってきやがる。日本人にとって、YUIという言葉は、普通Yahoo! User Interfaceなんかではなく、人の名前になる。しかも、女性の可能性が高いので、リファラーがYUIだらけに。

というわけで、YUIを最近使っているわけだが、イテレートするのにfor文を書くのが嫌なので、eachとmapだけでも、prototype.jsから移植してみた。勝手に、YAHOO.util.Collectionと名前を付けてみたりw。改め、YAHOO.utilx.Collectionにした。また、使いそうなコレクションがあったら追加してみる予定。別に、anyやらallやら全部prototype.jsから持ってきてもいいのだけど、面倒なので必要があれば実装するし、使わなければ実装しない。
というわけで、以下がソース

  1. YAHOO.namespace('utilx');
  2. YAHOO.utilx.Collection = function() {
  3.   var _each = function(data, iterator) {
  4.     if (YAHOO.lang.isArray(data)) {
  5.       for (var i = 0, l = data.length; i <l; i++) {
  6.         iterator(data[i]);
  7.       }
  8.     } else if (data && typeof data === 'object') {
  9.       for (var property in data) {
  10.         iterator({key: property, value:data[property]});
  11.       }
  12.     }
  13.   };
  14.   return {
  15.     $break: {},
  16.     $continue: new Error('"throw $continue" is deprecated, use "return" instead'),
  17.     each: function(data, iterator) {
  18.       var index = 0;
  19.       try {
  20.         _each(data, function(value) {
  21.           iterator(value, index++);
  22.         });
  23.       } catch (e) {
  24.         if (e != this.$break) throw e;
  25.       }
  26.       return data;
  27.     },
  28.     map: function(data, iterator) {
  29.       var results;
  30.       this.each(data, function(value, index) {
  31.         var result = (iterator || function(v) { return v;})(value, index);
  32.         if (YAHOO.lang.isArray(data)) {
  33.           results = results || [];
  34.           results.push(result);
  35.         } else {
  36.           results = results || {};
  37.           if (result && result['key'] && result['value']) {
  38.             results[result['key']] = result['value'];
  39.           }
  40.         }
  41.       });
  42.       return results;
  43.     }
  44.   }
  45. }();

次のように使う。配列のとき

  1. var hogeArray = ["foo", "bar", "baz"];
  2. YAHOO.utilx.Collection.each(hogeArray, function(data) {
  3.     console.log(data + "hogehoge")
  4. });
  5. hogeArray = YAHOO.utilx.Collection.map(hogeArray, function(data) {
  6.   if (data == 'baz') {
  7.     throw YAHOO.utilx.Collection.$break;
  8.   }
  9.   data = data + "hogehoge";
  10.   return data;
  11. });
  12. console.log(hogeArray);

オブジェクトのとき

  1. var hogeObj = {"foo": "This is Foo", "bar": "This is Bar", "baz": "This is Baz"};
  2. YAHOO.utilx.Collection.each(hogeObj, function(data) {
  3.     console.log(data.value + "hogehoge")
  4. });
  5. hogeObj = YAHOO.utilx.Collection.map(hogeObj, function(data) {
  6.   if (data.key == 'baz') {
  7.     throw YAHOO.utilx.Collection.$break;
  8.   }
  9.   data.value = data.value + "hogehoge";
  10.   return data;
  11. });
  12. console.log(hogeObj);

とりあえず、for文を書きたくないので、これで少しすっきりした。

(more...)

11月 14, 2007
» JavaScript Module Patternいいね。半年遅れだけど。。。

Module Pattern祭があったのは、今年の6月だったみたいですね。まだ時代の流れに遅れていますが、少しずつ追いかけていこうと思います。

現在は、jQueryでもprototype.jsでもなく、YUIを調査しています。まだまだ頭がprototype.js脳なので、多少苦しんでいますが、だいぶ掴めてきました。そして、YUIでも直接呼び出せばいいような小さなプログラムは書けるようになってきました。

ところで、prototype.jsでは、Class.createされるとinitializeが呼ばれ、ほげほげするように書きますよね。

  1. var Hoge = Class.create();
  2. Hoge.prototype = {
  3.   // public vars
  4.   varA: "hogehoge",
  5.   varB: "foobar",
  6.  
  7.   initialize: function() {
  8.     console.log("initialized");
  9.   },
  10.  
  11.   alertA: function() {
  12.     alert(this.varA);
  13.   }
  14. }
  15. var hoge = new Hoge();
  16. hoge.alertA();

とか。そして、それなりに大きいプログラムでもクラスごと(こんなことを言ったら怒られる?)に分けて、整理して構成することができますね。

どうやったら上のように、そしてYUIっぽく書けるかな、と昨日の晩から調査をしていました。そして、ようやくその手がかりとなるポストを見つけることができました。(別にYUI以外でもこの書き方していいけど。。。)
A JavaScript Module Patternです。名前はこのポストの著者のEricさんが勝手に付けたっぽいのですが、いいです。これ。日本でも半年前に話題になっていたみたいですね。時代遅れですいません。

クロージャを使うことによって、privateメンバっぽく書けるのもうれしいですね。上の例では、結局varAとvarBはpublicメンバとして評価されてしまうので、適当なコーディング規約がないとグループで開発したときに呼び出してしまいそうです。

そして、そのポストで紹介されていたような書き方は以下の通り。

  1. var Hoge = function() {
  2.   console.log("initialized");
  3.   var varA = "hogehoge";
  4.   var varB = "foobar";
  5.  
  6.   return {
  7.     alertA: function() {
  8.       alert(varA);
  9.     }
  10.   }
  11. }();
  12. var hoge = Hoge;
  13. hoge.alertA();

これだとvarAとvarBがHogeの中からしか参照できません。そして、returnでalertAという関数オブジェクト(メソッドのように使う)を持ったオブジェクトを返してくれます。

これからはこれで行こうと思います。

また、JavaScriptでは、無駄にnewをしない方がいいようです。YUIの調査をしていたら私のヒーローのDouglasさんがそうおっしゃっています。
JavaScript, We Hardly new Ya
なんでもprototypeオブジェクトを無駄に持ってしまうことが問題であるとおっしゃっているのです。つまり、メモリの無駄遣いは止めましょうとのことです。そのため、newしない方がいいそうです。というか、newすると時代遅れだそうです。

てっきり、prototypeオブジェクトにいろいろ書いておくと、newされたオブジェクトが使いまわしてくれるので、積極的にprototypeオブジェクトを使用して、newでオブジェクト生成し、それを使っていくのが通だと思っていたのですが、そうでもないのですね。

ただDouglasさんは、newを使用するときもなくはない、と言っています。私のpoorな頭ではどんなときにnewを使用した方がよいのか、ということがわかりませんでした。何度もそのオブジェクトを作成する場合と理解したらいいのでしょうか?

どちらにせよ、Douglasさんを信じて、あまりnewをしないように、prototypeオブジェクトは使用しないように今回は作っていこうと思います。

しかし、まだまだprototype.js脳から離れられていないので、Array.eachやらArray.mapやらのイテレーション系の関数がないのは苦しいです。自作しようかしらん。

次は、CustomEventが使えるようになりたいです。この辺がまだ理解できていませんので、読んでいきます。きっとここら辺が理解できたら、YUIでそれなりのプログラムが書けそうです。
Event-Driven Web Application Design
The Bubbling Technique & Custom Event, YUI’s Secret Weapon by Caridy Patiño Mayea

10月 19, 2007
» removeChildをするときには、その前にEventを全部取ること!

removeChildのみならずreplaceChildのときも必要!さらに、innerHTMLで内でEventを指定していた要素にも必要!

前からIE6にはメモリリークがあるってことは知っていた。そして、要素同士が双方にポインタを持つこと?で、そのメモリリークが起こると知ってはいた。そして、それを避けるべきだということも言葉では理解していた。でも、実際にどうすればいいの?ってことがわからなかったのだ!そして、つい先ほど、その問題解決がわかった!!!超感動!

確かに最近開発したWebアプリでは、再読み込みをする度に重くなっていき、絶対メモリリークしているなってことに気づいていた。でも、だからってどうしたらいいかはわからなかった。って、それはそれで問題なのだけど、こうやってわかった以上は早速月曜日に修正するぜ!

で、どうやって気づいたかというと、YUI TheaterのDouglas Crockford氏のThe Theory of the DOMからだった。いいよ!いいよ!Qualityも良かったけど、本当に勉強になるっす。一応3つも映像があって、全部で一時間半弱なんだけど、見てよかった!ここには、そのメモリリークについて話があったPart3を貼り付けておく。メモリリークの話だけなら、Part3の最初の6分くらいなので、それだけチェックしてもいいかも。

で、講義の中で上げられていた削除する前にイベントを削除する方法としては、上の映像では次の関数を使用していた。実際に動かしていないから間違っているかもしれんけど、だいたいこんな感じ。

  1. function purgeEventHandlers(node) {
  2.   walkTheDOM(node, function(e) {
  3.     for (var n in e) {
  4.       if (typeof e[n] === 'function') {
  5.         e[n] = null;
  6.       }
  7.     }
  8.   });
  9. }
  10. function walkTheDOM(node, func) {
  11.   func(node);
  12.   node = node.firstChild;
  13.   while (node) {
  14.     walkTheDOM(node, func);
  15.     node = node.nextSibling;
  16.   }
  17. }

もしくはYUIのpurgeElementで指定した要素以下の要素のイベントを一掃してくれるとのことだ。
まぁ、方法がわかれば簡単に作れるさ。prototype.jsなどでElement.remove()とかしているところもremoveする前にイベントを一掃しないとメモリリークしてしまうので、気をつけないといけない。私の問題はまさにこれだったと思う。というわけで、Douglasさんのファンになりましたので、当分YUIを調べることになりそうす。
うーむ。やってみたけど、メモリが開放されない。。。なんでだろ。dragdrop.jsのdraggableをたくさん作って、その上のDIVコンテナのinnerHTMLを書き換えているので、おそらくdraggableな要素が無くなってもメモリは持っているかのようだ。。。ちょっと調査せなかんな。

つーか、Wordpressのバージョンを上げたらwysiwygエディタを消すオプションが無くなってた。。。orz
ソースコードのスペースを勝手に削りやがって!!!!空行も削りやがる。許せん!くそー。腹が立ってきた。
って、少し調べたら私の他にも腹が立っている人はいるみたい。
Idea: Option to turn off WYSIWYG editor, sitewide!
解決方法がtinymceのディレクトリを削除しろだって?やってらんねー。
(more...)

10月 11, 2007
» つーか、全く速くならんかった。

昨日からはてブで賑わっている「一行でIEのJavaScriptを高速化する方法」を試したのだけど、私の環境では全く速くならんかった。記事を読んだときは、私も「これはすごい」と思って、今朝早速、最近ずっと開発していた場所に組み込んでベンチを取ってみたのだけど、全く変わらないという結果が出た。 まず、 IE では document にそのままアクセスすると window オブジェクトの内部メソッドが実行されてしまいます。これが非常に重いのです。 まぁ、確かに理解はできるし、軽くなりそうな気がするけども、実際はdocumentを呼ぶ機会は少ない(DOMで検索した要素もキャッシュ化している)ので、高速化されなかったってことかな。きっと、 この方法は document と書かれた部分を 5 倍以上(ループのコストを引くとたぶん 10 倍以上?)速くすることができるのですが、その数倍というのは「プロパティアクセス」と「関数呼び出し」の差です(関数呼び出しを減らしていると考えてください)。 ですので、ほとんどのウェブサイトでは効果はあまり感じられないかもしれません。 とのことなので、私のスクリプトが重いのはDOM検索なので、変わらなかったんだろうな。 つーか、変数の初期化に関しては、勉強になったよ。特にJavaScript高速化 - まさにっき(使えないプログラマーの記録)の「とおりすがり」氏、説明わかりやすい!というわけで、私もメモとして書かしてもらおうっと。 とおりすがり 『> これもまた、 JavaScript では変数はスコープの先頭で生成されるため、 document は空の変数となり undefined になってしまいます。 例えば var foo = 1, bar = 2; var foobar = foo + bar; なんていう宣言があった場合、 var foo = bar = foobar = undefined; //宣言された変数はスコープの最初に全部まとめて生成される。 foo = 1; bar = 2; foobar = foo + bar; みたいに解釈されます。なので var [...]

» つーか、全く速くならんかった。

昨日からはてブで賑わっている「一行でIEのJavaScriptを高速化する方法」を試したのだけど、私の環境では全く速くならんかった。記事を読んだときは、私も「これはすごい」と思って、今朝早速、最近ずっと開発していた場所に組み込んでベンチを取ってみたのだけど、全く変わらないという結果が出た。

まず、 IE では document にそのままアクセスすると window オブジェクトの内部メソッドが実行されてしまいます。これが非常に重いのです。

まぁ、確かに理解はできるし、軽くなりそうな気がするけども、実際はdocumentを呼ぶ機会は少ない(DOMで検索した要素もキャッシュ化している)ので、高速化されなかったってことかな。きっと、

この方法は document と書かれた部分を 5 倍以上(ループのコストを引くとたぶん 10 倍以上?)速くすることができるのですが、その数倍というのは「プロパティアクセス」と「関数呼び出し」の差です(関数呼び出しを減らしていると考えてください)。

ですので、ほとんどのウェブサイトでは効果はあまり感じられないかもしれません。

とのことなので、私のスクリプトが重いのはDOM検索なので、変わらなかったんだろうな。

つーか、変数の初期化に関しては、勉強になったよ。特にJavaScript高速化 - まさにっき(使えないプログラマーの記録)の「とおりすがり」氏、説明わかりやすい!というわけで、私もメモとして書かしてもらおうっと。

とおりすがり 『> これもまた、 JavaScript では変数はスコープの先頭で生成されるため、 document は空の変数となり undefined になってしまいます。

例えば var foo = 1, bar = 2; var foobar = foo + bar; なんていう宣言があった場合、
var foo = bar = foobar = undefined; //宣言された変数はスコープの最初に全部まとめて生成される。
foo = 1; bar = 2; foobar = foo + bar;
みたいに解釈されます。なので
var doc = document; var document = doc; は
var doc = document = undefined; doc = document; document = doc;
になって undefined になるんですよ。

> 2行目の時点での doc が undefined になっているのか?
var document; がある時点で、そのスコープに入った瞬間に document が undefined になるわけです。
と、全然関係ない通りすがりでした。』 (2007/10/11 08:05)

あと、amachangさんの言っているevalのタイミングについても勉強になった。

eval で var 宣言することでスコープ途中から変数を生成することができるのです。

しかし、

  1. /*@cc_on _d=document;eval('var document=_d')@*/

といった、いかにも黒魔術的な書き方はハックって感じがするね。

9月 30, 2007
» JavaScriptで重い処理をするとアニメーションGIFが止まる件について

当分の間JavaScriptオンリーです。今日はですます調です。 Ajaxのローディングとかでクルクル回るアニメーションGIFてありますよね?あのローディングのアニメーションGIFをJavaScriptで重い処理をするときに使おうと思っていてその画像を探していました。そして、先日調べていたら、こんなサイトを発見しました。激しくいいですね。 Ajaxload - Ajax loading gif generator 実は、結構有名なサイトなんですね。知らなかったです。でも、先日知って、早速作ってみました。 そして、早速使おうと思ったのですが、重い処理をしている間にアニメーションGIFを表示させていてもアニメーションがされないのです。。。つまり、アニメーションではない状態のGIF画像が表示されるだけなのです。その原因はJavaScriptが処理されている間だからです。また、重いJavaScriptが処理中の際にはその間はブラウザが固まります。 その解決方法を探していたのですが、その際にようやく私もsetTimeoutの使いかたがわかりました。そして、ページ描画のタイミングを制御できるようになりました。そうです。ポイントはsetTimeoutです。重い処理を一つの関数に入れちゃダメなんですね。つまり、これはダメなのです。 JAVASCRIPT: Event.observe(window, 'load', function() {         var loadingImage = Builder.node('img', {src: 'loading.gif'});         var sync = $('sync');         var async = $('async');         var loading = $('loading');         var working = $('working');         var loop = $('loop');       [...]

» JavaScriptで重い処理をするとアニメーションGIFが止まる件について

当分の間JavaScriptオンリーです。今日はですます調です。

Ajaxのローディングとかでクルクル回るアニメーションGIFてありますよね?あのローディングのアニメーションGIFをJavaScriptで重い処理をするときに使おうと思っていてその画像を探していました。そして、先日調べていたら、こんなサイトを発見しました。激しくいいですね。
Ajaxload - Ajax loading gif generator
実は、結構有名なサイトなんですね。知らなかったです。でも、先日知って、早速作ってみました。
ローディング画像

そして、早速使おうと思ったのですが、重い処理をしている間にアニメーションGIFを表示させていてもアニメーションがされないのです。。。つまり、アニメーションではない状態のGIF画像が表示されるだけなのです。その原因はJavaScriptが処理されている間だからです。また、重いJavaScriptが処理中の際にはその間はブラウザが固まります。

その解決方法を探していたのですが、その際にようやく私もsetTimeoutの使いかたがわかりました。そして、ページ描画のタイミングを制御できるようになりました。そうです。ポイントはsetTimeoutです。重い処理を一つの関数に入れちゃダメなんですね。つまり、これはダメなのです。

  1. Event.observe(window, 'load', function() {
  2.         var loadingImage = Builder.node('img', {src: 'loading.gif'});
  3.         var sync = $('sync');
  4.         var async = $('async');
  5.         var loading = $('loading');
  6.         var working = $('working');
  7.         var loop = $('loop');
  8.         Event.observe(sync, 'click', function() {
  9.           var breakNumber = 500;
  10.           var callback = function() {
  11.             // ここが重いということにする。例えば、以下はまぁまぁ重い。
  12.             while (breakNumber--) {
  13.               working.appendChild(Builder.node('div', {className: 'test'}));
  14.               document.getElementsByClassName('test');
  15.             }
  16.             loading.removeChild(loading.firstChild);
  17.             loop.innerHTML = 501 - breakNumber;
  18.             sync.disabled = false;
  19.           }
  20.           loading.appendChild(Builder.node('div', [loadingImage, "Loading..."]));
  21.           sync.disabled = true;
  22.           setTimeout(callback, 0);
  23.         });
  24.      });

あ。ちなみにprototype.jsとscript.aculo.usがあることを前提に書いているので、その辺は適当にほげほげしてください。まぁ、ここではsetTimeoutを使用して、その中で重い処理を実行していますね。処理を始める前にアニメーション画像を出して、処理が終わる際にローディングの画像を消す処理をしています。そして、while文の中を勝手に重い処理としてここでは使用しています。しかし、このwhile文が流れている間、いや正確にはcallback関数が実行されている間はアニメーションGIFが止まります。

そこで、アニメーションGIFを止めないような方法を考えました。その根本にある考えは。。。重い処理は一つの関数に入れないということです。つまり、一つ関数の中にループで重い処理を実行させるのではなく、再帰を使用して何個も関数を呼び出す必要があります。もちろんそれが再帰処理ができないような重い処理であればこの方法は無理ですが、往々にして重い処理というのはDOMを使ってイテレーションを使用するところになる可能性が高いと思いますので、それを再帰処理に置き換えましょう。そして、修正したコードは以下の通りです。

  1. Event.observe(window, 'load', function() {
  2.         var loadingImage = Builder.node('img', {src: 'loading.gif'});
  3.         var sync = $('sync');
  4.         var async = $('async');
  5.         var loading = $('loading');
  6.         var working = $('working');
  7.         var loop = $('loop');
  8.         Event.observe(async, 'click', function() {
  9.           var breakNumber = 500;
  10.           var callback = function() {
  11.             if (breakNumber--) {
  12.               working.appendChild(Builder.node('div', {className: 'test'}));
  13.               document.getElementsByClassName('test');
  14.               setTimeout(callback, 0);
  15.             } else {
  16.               loading.removeChild(loading.firstChild);
  17.               async.disabled = false;
  18.             }
  19.             loop.innerHTML = 501 - breakNumber;
  20.           };
  21.           loading.appendChild(Builder.node('div', [loadingImage,"Loading..." ]));
  22.           async.disabled = true;
  23.           setTimeout(callback,0);
  24.        });
  25.       });

これで、重いループををしていても、だいたいの場合はアニメーションGIFが止まらずに済みます。「だいたいの場合」と書いたのは理由があります。実は、私の実環境では、もっと重い処理をしなければいけない用件がありましたので、修正版でもアニメーションGIFが動きませんでした。正確にはfirefoxでは大丈夫でした(たぶんそれほど重くなかったのが原因かもしれません。)が、IE6ではアニメーションGIFが止まりました。原因は、再帰の中で呼び出しているsetTimeoutのmillisecondの値でした。私はどうせ次の処理を実行してもらうのだから0でいいのではないか、と考えていたのですが、0だとすぐ関数をエンキューするのはいいのですが、現在走っている処理が終わると、すぐキューにたまった関数をデキューして実行するので、JavaScriptの処理が常に動いていることになったようです。あくまで想像ですが。なので、ここにラグを与える必要があったのです。そこで、setTimeoutに渡すmillisecondの値を適当な数値に変更すると、カクカクするけど、アニメーションGIFが動いてくれました。
動くサンプルは以下に置いておきます。ちなみにこのサンプルはカクカクしません。
Animation GIF and JavaScript setTimeout

404 Blog Not Found:javascript - ページはいつ再描画されるかが激しく参考になりました。

先日、jQuery開発者向けメモを見て、一通り試してみましたがなかなか直感的に書けそうなライブラリなので今後使用することを検討してみます。今、開発の勢いが一番あるライブラリなので追いかけたりするのが楽しそうですね。prototype.jsとscript.aculo.usは、なかなか新たなリリースが無く、ちょっとさみしいですが、これはこれでライブラリとして成熟段階に入ったといことでアリなのでしょうね。

さて、本日はTOEICを受けてきました。過去に2回ほど受けたのですが、相変わらず時間が足らなかったです。問題が中途半端に全問解けそうなので、ついついちゃんと読んでやってしまうのが原因です。そのため最後に15問ほど残ってしまいます。スピード勝負はつらいですね。でもまぁ、おそらくそれなりのスコアは出たでしょう。試験地の愛知大学が思ったよりもとてもキレイでした。

先日激しくヘコむことがありましたが、ようやくすっきりしました。今日から強く生きていこうと思います。

9月 24, 2007
» DOM検索効率化のメソッド群を作ってみた。

というわけで、もういっちょ。考えていたDOM検索効率を考えたメソッド群を作ってみた。 ええと、prototype.jsのElementオブジェクトにaddMethodsしていることからもわかるようにprototype.js必須っす。ええと、私の使っているprototype.jsは、1.5.1ね。script.aculo.us(v1.7.1_beta3)と一緒に使っているので。 JAVASCRIPT: document.getElementsByClassNameAndTagName = function(className, parentElement, tagName) {   if (Prototype.BrowserFeatures.XPath) {      return $(parentElement).getElementsByClassName(className);   } else {     var children = $(parentElement).getElementsByTagName(tagName || '*');     var elements = [], child;     for (var i = 0, length = children.length; i <length; i++) {       child = children[i];       if (Element.hasClassName(child, className))         elements.push(Element.extend(child));   [...]

» DOM検索効率化をもう少し追ってみる。

やったよ。やった。ようやくキーボードが届いたよ。ここ3ヶ月、私のキーボードはC-xと]}のキーが壊れていたのだけど、ようやく快適に打てるようになったよ。私の状態を見兼ねて買ってくれた学生時代の恩師の稲葉先生どうもありがとうございます。つーか、そのくらい自分で買えばいいのだけど、なんか買う気がなかなか起こらなくて、ダラダラしてました。。。 で、さっそく簡単なプログラムを書きはじめている。えと、実はまだDOM検索周りを調べているのだけど、一つ疑問があがってきた。それは以下の通り。 childNodesでの検索とfirstChildとnextSiblingの検索について。つまり、どちらでも子ノードを取得することができるのだけど、実際どっちを使ったらいいの? ググってみるとパフォーマンスを見ている人がいたのだが、childNodesよりも、fistChildを取ってnextSiblingで検索する方が速いということらしい。特に、IE6が良くないとのこと。ブラウザの実装によるのね。。。 new Blah().list(); node.childNodes[] performance うーむ。てっきりnextSiblingって、いかにもリンクリストな感じでその要素の数を線形探索しないといけないから、O(log(n))で、childNodesでindex指定で取得したら、O(1)で速いんだろうなぁ、なんて思っていたのだけど、どうやらそうでないみたい。。。と鵜呑みにするのもどうか、と思うのだが、まぁ、いいや。 ええと、何でこんなことを調べているかと言うと、私はchildNodesからelementNodeだけの配列を取得するようなメソッドが欲しかったので、作ろうと思っていたのだ。そして、その際に、firstChildからnextSiblingでグルングルン回すか、childNodes分こっちもグルングルン回するかどっちにしようかな、と思っていたのだ。そこで自分でベンチマークを取ってみたよ。まぁ、結局、全てを線形探索するのだから、どっちでもいいような気がするけども。。。で、ベンチマークツールはamachang氏のbenchmark.jsを使用した。 ソースはこんな感じ。ここにサンプルも置いておくよ。childNodes && firstChild + nextSibling Benchmark HTML: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html lang="ja">   <head>     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">     </meta><meta http-equiv="Content-Style-Type" content="text/css">     </meta><meta http-equiv="Content-Script-Type" content="text/javascript">     <script language="JavaScript" type="text/javascript" src="js/prototype.js"></script>     <script language="JavaScript" type="text/javascript" src="js/scriptaculous.js"></script>     <script language="JavaScript" type="text/javascript" src="benchmark.js"></script>     <script language="JavaScript" type="text/javascript">       Element.addMethods({         [...]

» DOM検索効率化のメソッド群を作ってみた。

というわけで、もういっちょ。考えていたDOM検索効率を考えたメソッド群を作ってみた。

ええと、prototype.jsのElementオブジェクトにaddMethodsしていることからもわかるようにprototype.js必須っす。ええと、私の使っているprototype.jsは、1.5.1ね。script.aculo.us(v1.7.1_beta3)と一緒に使っているので。

  1. document.getElementsByClassNameAndTagName = function(className, parentElement, tagName) {
  2.   if (Prototype.BrowserFeatures.XPath) {
  3.      return $(parentElement).getElementsByClassName(className);
  4.   } else {
  5.     var children = $(parentElement).getElementsByTagName(tagName || '*');
  6.     var elements = [], child;
  7.     for (var i = 0, length = children.length; i <length; i++) {
  8.       child = children[i];
  9.       if (Element.hasClassName(child, className))
  10.         elements.push(Element.extend(child));
  11.     }
  12.     return elements;
  13.   }
  14. }
  15.  
  16. Element.addMethods({
  17.   getElementsByClassNameAndTagName: function(element, className, tagName) {
  18.     return document.getElementsByClassNameAndTagName(className, element, tagName);
  19.   },
  20.  
  21.   nextElement: function(element) {
  22.     do {
  23.       element = element.nextSibling;
  24.     } while (element && element.nodeType != 1);
  25.     return $(element);
  26.   },
  27.  
  28.   previousElement: function(element) {
  29.     do {
  30.       element = element.previousSibling;
  31.     } while (element && element.nodeType != 1);
  32.     return $(element);
  33.   },
  34.  
  35.   firstChildElement: function(element) {
  36.     var child = element.firstChild;
  37.     while (child && child.nodeType != 1) {
  38.       child = child.nextSibling;
  39.     }
  40.     return $(child);
  41.   },
  42.  
  43.   lastChildElement: function(element) {
  44.     var child = element.lastChild;
  45.     while (child && child.nodeType != 1) {
  46.       child = child.previousSibling;
  47.     }
  48.     return $(child);
  49.   },
  50.  
  51.   childElements: function(element) {
  52.     var children = [];
  53.     var child = element.firstChild;
  54.     while (child) {
  55.       if (child.nodeType == 1) {
  56.         children.push($(child));
  57.       }
  58.       child = child.nextSibling;
  59.     }
  60.     return children;
  61.   },
  62.  
  63.   childElement: function(element, index) {
  64.     var nodeIndex = 0;
  65.     var child = element.firstChild;
  66.     while (child) {
  67.       if (child.nodeType == 1 && index == nodeIndex++) {
  68.           return $(child);
  69.       }
  70.       child = child.nextSibling;
  71.     }
  72.     return null;
  73.   },
  74.  
  75.   cleanWhitespaceRecursive: function(element) {
  76.     var f = function(element) {
  77.       var child = $(element).cleanWhitespace().firstChild;
  78.       while (child) {
  79.         if (child.nodeType == 1) {
  80.           f(child);
  81.         }
  82.         child = child.nextSibling;
  83.       }
  84.     };
  85.     f(element);
  86.     return element;
  87.   }
  88. });

nextElement, previousElementはそのまんま。textNodeはすっ飛ばして、elementNodeだけを見ている。firstChildElementとlastChildElementはfirstElementとlastElementって命名しようかと思ったけど、childだということを意識したかったのでちょいと冗長だけど、これで堪忍してや。で、意味もそのまんま。textNodeをすっ飛ばして最初や最後のelementNodeを返す。

childElementsは、前回のポストの結果を考慮してfirstChildとnextSiblingを採用。elementNodeの配列を返す。childElementは、index指定でelementNodeを返す。本当は、こんな感じでchildElementsメソッドの返す配列のindexを返す方がすっきりしていていいんだけどなぁ。

  1. childElement: function(element, index) {
  2.      return $(element).childElements()[index];
  3.   },

しかし、それだとnextSiblingを全て見てまわってしまうので、遅くなりそうなので、不採用。

cleanWhitespaceRecursiveはついでの産物。今回作成したものとは関係がないのだけども、一応。HTMLコーダにじかにHTMLを書かれるとwhitespaceが入ってしまい、困るので再帰的に消してみることにした。JavaScriptで再帰をするときってやっぱり、ローカル変数に関数をぶちこんで、それを何度も呼び出す方がいいのかな、と思ったので、自分自身を何度も呼び出すような方は不採用。
自分自身を呼び出すのは、こんな感じか。

  1. cleanWhitespaceRecursive: function(element) {
  2.     element = $(element);
  3.     var child = element.cleanWhitespace().fistChild;
  4.     while (child) {
  5.       if (child.nodeType == 1) {
  6.         child.cleanWhitespaceRecursive();
  7.       }
  8.       child = child.nextSibling;
  9.     }
  10.     return element;
  11.   }

どっちがいいんだろうなぁ。。。

で、本当は、オプションで、$指定で返すか、そのままのelementを返すかを指定できるようにするかで迷ったのだけど、prototype.jsと一緒に使うことを前提としているので、$で返すようにした。これもパフォーマンスに関係してくるんだけど、まぁ、その辺は考慮中。

つーか、また後で追記したり、ソース修正するかもしれん。名前はなんでもよかったのだけど、domx.jsとしよう。

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  2. <html lang="ja">
  3.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  4.     <meta http-equiv="Content-Style-Type" content="text/css" />
  5.     <meta http-equiv="Content-Script-Type" content="text/javascript" />
  6.     <script language="JavaScript" type="text/javascript" src="js/prototype.js"></script>
  7.     <script language="JavaScript" type="text/javascript" src="js/scriptaculous.js"></script>
  8.     <script language="JavaScript" type="text/javascript" src="domx.js"></script>
  9.     <script language="JavaScript" type="text/javascript">
  10.     Event.observe(window, 'load', function() {
  11.       var mainContainer = $('main');
  12.       var fc = mainContainer.firstChildElement();
  13.       fc.style.backgroundColor = '#eee';
  14.  
  15.       var lc = mainContainer.lastChildElement();
  16.       lc.style.backgroundColor = '#aaa';
  17.  
  18.       var ce = mainContainer.childElement(1);
  19.       ce.style.backgroundColor = '#ddd';
  20.  
  21.       var ne = ce.nextElement();
  22.       ne.style.backgroundColor = '#ccc';
  23.  
  24.       var pe = lc.previousElement();
  25.       pe.style.backgroundColor = '#bbb';
  26.  
  27.       $A(mainContainer.childElements()).each(function(e) {
  28.         e.style.margin = '0.5em';
  29.       });
  30.  
  31.       var sushinoneta = mainContainer.getElementsByClassNameAndTagName('sushinoneta', 'input');
  32.       $A(sushinoneta).each(function(neta) {
  33.           neta.style.marginLeft = '20px';
  34.           switch (neta.name) {
  35.           case 'tako':
  36.             neta.value = '380';
  37.             break;
  38.           case 'ikura':
  39.             neta.value = '420';
  40.             break;
  41.           case 'uni':
  42.             neta.value = '600';
  43.             break;
  44.           default:
  45.             neta.value = '360';
  46.             break;
  47.           }
  48.       });
  49.       var before = $('before');
  50.       before.appendChild(Builder.node('h1', 'Before cleanWhitespaceRecursive'));
  51.       before.appendChild(Builder.node('textarea', {cols: '100', rows: '5'}, mainContainer.innerHTML));
  52.       mainContainer.cleanWhitespaceRecursive();
  53.       var after = $('after');
  54.       after.appendChild(Builder.node('h1', 'After cleanWhitespaceRecursive'));
  55.       after.appendChild(Builder.node('textarea', {cols: '100', rows: '5'}, mainContainer.innerHTML));
  56.     });
  57.     </script>
  58.     <title>DOMX Demo</title>
  59.   </head>
  60.     <div id="main">
  61.       <div>
  62.         <label>
  63.           <span>tako:</span>
  64.           <input type="text" name="tako" class="sushinoneta" />
  65.         </label>
  66.       </div>
  67.       <div>
  68.         <label>
  69.           <span>ika:</span>
  70.  
  71.           <input type="text" name="ika" class="sushinoneta" />
  72.         </label>
  73.       </div>
  74.       <div>
  75.         <label>
  76.           <span>uni:</span><input type="text" name="uni" class="sushinoneta" />
  77.         </label>
  78.       </div>
  79.  
  80.       <div>
  81.         <label>
  82.           <span>ikura:</span><input type="text" name="ikura" class="sushinoneta" />
  83.         </label>
  84.       </div>
  85.       <div>
  86.         <label>
  87.           <span>maguro:</span>
  88.  
  89.           <input type="text" name="maguro" class="sushinoneta" />
  90.         </label>
  91.       </div>
  92.     </div>
  93.     <div id="before"></div>
  94.     <div id="after"></div>
  95.   </body>
  96. </html>

ソースはここ。

domx.js
ライセンスは適当っす。自己責任で使ってちょ。

で、上のソースを動かす形にしたデモもここに置いておく。domx demo

うーん。そろそろjQuery使ってみようかなぁ。。。onReadyとか便利そうだし。。。インデントがタブなのが非常に嫌なので、敬遠しているのだけど。。。

全然関係ないが、ここ二日ほど愛知県図書館で開発をしている。なかなか良いね。ホットスポットもあるみたいだけど、契約をしていないのでインターネットにつなげない。でも、そのおかげで効率がいいよん。インターネットがあるとダラダラしちゃって、ダミだ。

» DOM検索効率化をもう少し追ってみる。

やったよ。やった。ようやくキーボードが届いたよ。ここ3ヶ月、私のキーボードはC-xと]}のキーが壊れていたのだけど、ようやく快適に打てるようになったよ。私の状態を見兼ねて買ってくれた学生時代の恩師の稲葉先生どうもありがとうございます。つーか、そのくらい自分で買えばいいのだけど、なんか買う気がなかなか起こらなくて、ダラダラしてました。。。

で、さっそく簡単なプログラムを書きはじめている。えと、実はまだDOM検索周りを調べているのだけど、一つ疑問があがってきた。それは以下の通り。

childNodesでの検索とfirstChildとnextSiblingの検索について。つまり、どちらでも子ノードを取得することができるのだけど、実際どっちを使ったらいいの?

ググってみるとパフォーマンスを見ている人がいたのだが、childNodesよりも、fistChildを取ってnextSiblingで検索する方が速いということらしい。特に、IE6が良くないとのこと。ブラウザの実装によるのね。。。
new Blah().list(); node.childNodes[] performance
うーむ。てっきりnextSiblingって、いかにもリンクリストな感じでその要素の数を線形探索しないといけないから、O(log(n))で、childNodesでindex指定で取得したら、O(1)で速いんだろうなぁ、なんて思っていたのだけど、どうやらそうでないみたい。。。と鵜呑みにするのもどうか、と思うのだが、まぁ、いいや。

ええと、何でこんなことを調べているかと言うと、私はchildNodesからelementNodeだけの配列を取得するようなメソッドが欲しかったので、作ろうと思っていたのだ。そして、その際に、firstChildからnextSiblingでグルングルン回すか、childNodes分こっちもグルングルン回するかどっちにしようかな、と思っていたのだ。そこで自分でベンチマークを取ってみたよ。まぁ、結局、全てを線形探索するのだから、どっちでもいいような気がするけども。。。で、ベンチマークツールはamachang氏のbenchmark.jsを使用した。

ソースはこんな感じ。ここにサンプルも置いておくよ。childNodes && firstChild + nextSibling Benchmark

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  2. <html lang="ja">
  3.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  4.     </meta><meta http-equiv="Content-Style-Type" content="text/css">
  5.     </meta><meta http-equiv="Content-Script-Type" content="text/javascript">
  6.     <script language="JavaScript" type="text/javascript" src="js/prototype.js"></script>
  7.     <script language="JavaScript" type="text/javascript" src="js/scriptaculous.js"></script>
  8.     <script language="JavaScript" type="text/javascript" src="benchmark.js"></script>
  9.     <script language="JavaScript" type="text/javascript">
  10.       Element.addMethods({
  11.         _childElementsByFirstChildNextSibling: function(element) {
  12.           var children = [];
  13.           var child = element.firstChild;
  14.           while (child) {
  15.             if (child.nodeType == 1) {
  16.               children.push(child);
  17.             }
  18.             var child = child.nextSibling;
  19.           }
  20.           return children;
  21.         },
  22.         _childElementsByChildNodes: function(element) {
  23.           var children = [];
  24.           var nodes = element.childNodes;
  25.           var child = null;
  26.           for (var i = 0, l = nodes.length; i <l; i++) {
  27.             child = nodes.item(i);
  28.             if (child.nodeType == 1) {
  29.               children.push(child);
  30.             }
  31.     &n