GANCHIKU.com

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の方では、動かないバージョンでは以下のようになっています。

Event.observe(window, 'load', function() {
  $A($('scroll-b').getElementsByClassName('drag-obj')).each(function(obj) {
    new Draggable(obj, { revert: true, scroll: 'scroll-b' });
  });
  Droppables.add('drop-container', {
    hoverclass: 'dragdrop-in',
    onDrop: function(obj, target) { target.innerHTML += obj.innerHTML+""; }
  });
});

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

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

#scroll-a {
  float: left;
  width: 70%;
  height: 100%;
  overflow-y: scroll;
}

#scroll-b {
  height: 100%;
  overflow-y-: scroll;
}

別に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でうれしい限りです。

for文書きたくない。

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

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

YAHOO.namespace('utilx');
YAHOO.utilx.Collection = function() {
  var _each = function(data, iterator) {
    if (YAHOO.lang.isArray(data)) {
      for (var i = 0, l = data.length; i < l; i++) {
        iterator(data[i]);
      }
    } else if (data && typeof data === 'object') {
      for (var property in data) {
        iterator({key: property, value:data[property]});
      }
    }
  };
  return {
    $break: {},
    $continue: new Error('"throw $continue" is deprecated, use "return" instead'),
    each: function(data, iterator) {
      var index = 0;
      try {
        _each(data, function(value) {
          iterator(value, index++);
        });
      } catch (e) {
        if (e != this.$break) throw e;
      }
      return data;
    },
    map: function(data, iterator) {
      var results;
      this.each(data, function(value, index) {
        var result = (iterator || function(v) { return v;})(value, index);
        if (YAHOO.lang.isArray(data)) {
          results = results || [];
          results.push(result);
        } else {
          results = results || {};
          if (result && result['key'] && result['value']) {
            results[result['key']] = result['value'];
          }
        }
      });
      return results;
    }
  }
}();

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

var hogeArray = ["foo", "bar", "baz"];
YAHOO.utilx.Collection.each(hogeArray, function(data) {
    console.log(data + "hogehoge")
});
hogeArray = YAHOO.utilx.Collection.map(hogeArray, function(data) {
  if (data == 'baz') {
    throw YAHOO.utilx.Collection.$break;
  }
  data = data + "hogehoge";
  return data;
});
console.log(hogeArray);

オブジェクトのとき

var hogeObj = {"foo": "This is Foo", "bar": "This is Bar", "baz": "This is Baz"};
YAHOO.utilx.Collection.each(hogeObj, function(data) {
    console.log(data.value + "hogehoge")
});
hogeObj = YAHOO.utilx.Collection.map(hogeObj, function(data) {
  if (data.key == 'baz') {
    throw YAHOO.utilx.Collection.$break;
  }
  data.value = data.value + "hogehoge";
  return data;
});
console.log(hogeObj);

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

続きを読む

JavaScript Module Patternいいね。半年遅れだけど。。。

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

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

ところで、prototype.jsでは、Class.createされるとinitializeが呼ばれ、ほげほげするように書きますよね。
[javascript]
var Hoge = Class.create();
Hoge.prototype = {
// public vars
varA: “hogehoge”,
varB: “foobar”,

initialize: function() {
console.log(“initialized”);
},

alertA: function() {
alert(this.varA);
}
}
var hoge = new Hoge();
hoge.alertA();
[/javascript]
とか。そして、それなりに大きいプログラムでもクラスごと(こんなことを言ったら怒られる?)に分けて、整理して構成することができますね。

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

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

そして、そのポストで紹介されていたような書き方は以下の通り。
[javascript]
var Hoge = function() {
console.log(“initialized”);
var varA = “hogehoge”;
var varB = “foobar”;

return {
alertA: function() {
alert(varA);
}
}
}();
var hoge = Hoge;
hoge.alertA();
[/javascript]
これだと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

removeChildをするときには、その前にEventを全部取ること!

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

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

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

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

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

function purgeEventHandlers(node) {
  walkTheDOM(node, function(e) {
    for (var n in e) {
      if (typeof e[n] === 'function') {
        e[n] = null;
      }
    }
  });
}
function walkTheDOM(node, func) {
  func(node);
  node = node.firstChild;
  while (node) {
    walkTheDOM(node, func);
    node = node.nextSibling;
  }
}

もしくは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のディレクトリを削除しろだって?やってらんねー。
続きを読む

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

昨日からはてブで賑わっている「一行で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 宣言することでスコープ途中から変数を生成することができるのです。

しかし、
[javascript]
/*@cc_on _d=document;eval(‘var document=_d’)@*/
[/javascript]
といった、いかにも黒魔術的な書き方はハックって感じがするね。

Shin Ohno 2003-2012