script.aculo.usを読み解く

第11回 dragdrop.js (前編)

この記事を読むのに必要な時間:およそ 8.5 分

0046:  findDeepestChild: function(drops) {
0047:    deepest = drops[0];
0048:      
0049:    for (i = 1; i < drops.length; ++i)
0050:      if (Element.isParent(drops[i].element, deepest.element))
0051:        deepest = drops[i];
0052:    
0053:    return deepest;
0054:  },
0055:  

46~55行目のfindDeepestChildは,drops配列の要素の中で,もっとも深い子要素になるものを返す関数です。比較にはElement.isParentを使います。

Element.isParentの定義は947~953行目にあります。再帰的に,左辺の要素が右辺の要素に子孫として含まれるかどうかを調べます。

0947:// Returns true if child is contained within element
0948:Element.isParent = function(child, element) {
0949:  if (!child.parentNode || child == element) return false;
0950:  if (child.parentNode == element) return true;
0951:  return Element.isParent(child.parentNode, element);
0952:}
0953:
0056:  isContained: function(element, drop) {
0057:    var containmentNode;
0058:    if(drop.tree) {
0059:      containmentNode = element.treeNode; 
0060:    } else {
0061:      containmentNode = element.parentNode;
0062:    }
0063:    return drop._containers.detect(function(c) { return containmentNode == c });
0064:  },
0065:  

56~65行目のisContainedは,引数に,ドラッグ中の要素と,ドロップ先をとります。ドラッグ中の要素の親要素が,ドロップ先の_containersプロパティに列挙されているかを返す関数です。この判定が,そのドロップを受け付けるかどうかに使われます。

58行目で,Sortableクラスでは,ドロップ先がツリーとして振る舞う場合があります。そこで,drop.treeがtrueのときは,ツリーの根の要素を親要素と考え,element.treeNodeを参照します。

61行目で,通常は,element.parentNodeを参照します。

0066:  isAffected: function(point, element, drop) {
0067:    return (
0068:      (drop.element!=element) &&
0069:      ((!drop._containers) ||
0070:        this.isContained(element, drop)) &&
0071:      ((!drop.accept) ||
0072:        (Element.classNames(element).detect( 
0073:          function(v) { return drop.accept.include(v) } ) )) &&
0074:      Position.within(drop.element, point[0], point[1]) );
0075:  },
0076:

66~76行目のisAffectedは,引数に,ドラッグ中の座標と,ドラッグ中の要素と,ドロップ先をとります。この座標のドラッグをドロップ先が受けつけるかを返す関数です。

70行目で,上述のisContained関数の判定を調べます。

72行目で,ドロップ先のoptions.acceptに含まれるかを調べます。

74行目で,座標がドロップ先の要素の範囲内にあるか調べるために,Position.withinを使います。これらの条件をすべて満たしている必要があります。

0077:  deactivate: function(drop) {
0078:    if(drop.hoverclass)
0079:      Element.removeClassName(drop.element, drop.hoverclass);
0080:    this.last_active = null;
0081:  },
0082:

77~82行目のdeactivateは,引数にドロップ先をとります。ドロップ先からhoverclassのクラス名を削除したり,last_activeプロパティをnullにする関数です。

0083:  activate: function(drop) {
0084:    if(drop.hoverclass)
0085:      Element.addClassName(drop.element, drop.hoverclass);
0086:    this.last_active = drop;
0087:  },
0088:

83~88行目のactivateは,引数にドロップ先をとります。ドラッグが上空にあるドロップ先に,hoverclassのクラス名を追加したり,last_activeプロパティをセットする関数です。last_activeプロパティは,現在ドラッグが上空にあるドロップ先を示す値です。

0089:  show: function(point, element) {
0090:    if(!this.drops.length) return;
0091:    var drop, affected = [];
0092:    
0093:    this.drops.each( function(drop) {
0094:      if(Droppables.isAffected(point, element, drop))
0095:        affected.push(drop);
0096:    });
0097:        
0098:    if(affected.length>0)
0099:      drop = Droppables.findDeepestChild(affected);
0100:
0101:    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
0102:    if (drop) {
0103:      Position.within(drop.element, point[0], point[1]);
0104:      if(drop.onHover)
0105:        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
0106:      
0107:      if (drop != this.last_active) Droppables.activate(drop);
0108:    }
0109:  },
0110:

89~110行目のshowは,引数にドラッグ中の座標と,ドラッグ中の要素をとります。ドラッグが上空にあるドロップ先を見つけて処理をする関数です。

93行目で,ドロップ先すべてを,isAffected関数でチェックして,この座標,このドラッグ中の要素について,ドロップを受けつけるものをaffected配列に列挙します。

98行目で,affectedの候補がいくつかあった場合は,findDeepestChild関数で,もっともDOMツリーの階層が深い要素を選びます。

101行目で,前回の結果であるlast_activeと変化があったときは,前回の結果にdeactivate関数を呼び,107行目で,今回の結果にactivate関数を呼びます。

104行目で,onHoverフックがあれば呼びます。Position.overlapを呼び出していますが,この関数は直前に,Position.withinを呼ぶ必要があることに注意してください。それが103行目です。

著者プロフィール

源馬照明(げんまてるあき)

名古屋大学大学院多元数理科学研究科1年。学部生のときにSchemeの素晴らしさを知ったのをきっかけに,関数型言語の世界へ。JavaScriptに,ブラウザからすぐに試せる関数型言語としての魅力と将来性を感じている。

ブログ:Gemmaの日記