JavaScript研究 
top

Ext - extjs

JavaScriptフレームワーク Apolloって?

dragsortの解析

var dragsort = ToolMan.dragsort()
...
dragsort.makeListSortable(document.getElementById("numeric")), verticalOnly, saveOrder);

このコードの動作は、ページ中のあるリスト要素(idが”numeric”のもの)をドラッグ可能なリストに変換するというもの。

まず最初の行は、core.jsで定義されているメソッドを呼ぶ。

var ToolMan = {
  ...
  dragsort : function() {
    if (!ToolMan._dragsortFactory) throw "ToolMan DragSort module isn't loaded";
    return ToolMan._dragsortFactory
  },
  ...
}

ToolMan自体はdictionaryで、そのdragsortメンバには無名関数が割り当てられている。その結果、クラスのように振る舞う。このメソッドでpluggableな構造を作り出している。つまり、必要なければ読み込まなくても良い。使うなら読み込まれていなければならず、読み込まれていなければ例外を発生させる。

ToolMan._dragsortFactoryは、dragsort.jsで設定されていて、

ToolMan._dragsortFactory = {
  makeSortable : function(item) {
    ...
  }
  ...
}

といった感じで指定されている。インスタンス生成はされてないので、クラスメソッドを読んでいるような感じだ。

最初の呼び出しコードを再度見てみる。

dragsort.makeListSortable(document.getElementById("numeric")), verticalOnly, saveOrder);

これは、dragsort.jsのToolMan._dragsortFactory.makeListSortable()を呼ぶ。 makeListSortable

	/** 
	 * Iterates over a list's items, making them sortable, applying
	 * optional functions to each item.
	 *
	 * example: makeListSortable(myList, myFunc1, myFunc2, ... , myFuncN)
	 */
	makeListSortable : function(list) {
		var helpers = ToolMan.helpers()
		var coordinates = ToolMan.coordinates()
		var items = list.getElementsByTagName("li")

		helpers.map(items, function(item) {

コメントが動作を説明しているので、動作は想像できるがディーテールを見てみるといくつか分からないところがある。呼び出し側が引数3個なのに、function(list)と1つしか引数ととっていないように見えること。しかしコメントでは、myFunc1, myFunc2,…というように正しく処理されているように思える。#とりあえず放置。

list.getElementsByTagName(“li”)でLI要素のリストを作り、それに対してmap()で一括処理を行う。map関数はJavaScriptにはないようで、これをhelperクラスで実装している。ここでも無名関数を使ってその場でmap()の処理を書いている。

ここでのmap関数の処理は、 - li要素に対しmakeSortable()を呼ぶ。

	makeSortable : function(item) {
		var group = ToolMan.drag().createSimpleGroup(item)

		group.register('dragstart', this._onDragStart)
		group.register('dragmove', this._onDragMove)
		group.register('dragend', this._onDragEnd)

		return group
	},

最初の行のcreateSimpleGroup()は、drag.jsで定義されていて、

	createSimpleGroup : function(element, handle) {
		handle = handle ? handle : element
		var group = this.createGroup(element)
		group.setHandle(handle)
		group.transparentDrag()
		group.onTopWhileDragging()
		return group
	},

今度は呼び出し側の引数が1つなのに、2つ受け取るようになっている。想像としてはこの場合nullかundefあたりが渡され次の行での判定が行われるだろう。handle=elementとなるに違いない。createGroup()は、

	createGroup : function(element) {
		var group = new _ToolManDragGroup(this, element)

		var position = ToolMan.css().readStyle(element, 'position')
		if (position == 'static') {
			element.style["position"] = 'relative'
		} else if (position == 'absolute') {
			/* for Safari 1.2 */
			ToolMan.coordinates().topLeftOffset(element).reposition(element)
		}

		// TODO: only if ToolMan.isDebugging()
		group.register('draginit', this._showDragEventStatus)
		group.register('dragmove', this._showDragEventStatus)
		group.register('dragend', this._showDragEventStatus)

		return group
	},

となっていて、最初の行でオブジェクトのインスタンスを生成しているに違いない。

function _ToolManDragGroup(factory, element) {
	this.factory = factory
	this.element = element
	this._handle = null
	this._thresholdDistance = 0
	this._transforms = new Array()
	// TODO: refactor into a helper object, move into events.js
	this._listeners = new Array()
	this._listeners['draginit'] = new Array()
	this._listeners['dragstart'] = new Array()
	this._listeners['dragmove'] = new Array()
	this._listeners['dragend'] = new Array()
}

これはなぜか関数の形をしている。factoryには先ほどのToolMan._dragFactoryが設定される。まあともかくこれはドラッグ処理を司るデータ構造だろう。で、これとは別に_ToolManDragGroup.prototypeというのがあり、こちらにはメソッドがいくつか定義されている。#prototypeと関数の役割は?

createGroup()に戻る。

	createGroup : function(element) {
		var group = new _ToolManDragGroup(this, element)

		var position = ToolMan.css().readStyle(element, 'position')
		if (position == 'static') {
			element.style["position"] = 'relative'
		} else if (position == 'absolute') {
			/* for Safari 1.2 */
			ToolMan.coordinates().topLeftOffset(element).reposition(element)
		}

		// TODO: only if ToolMan.isDebugging()
		group.register('draginit', this._showDragEventStatus)
		group.register('dragmove', this._showDragEventStatus)
		group.register('dragend', this._showDragEventStatus)

		return group
	},

li要素のスタイル属性positionがstaticならばrelativeに書き換える。先ほど作ったgroupインスタンスのregisterメソッドを呼ぶ。これはgroupのそれぞれのイベントリスナーに、指定されたメソッド(ここでは_showDragEventStatusメソッド)を追加する。window.statusに現在実行中のイベント名が表示されるはずなのだが、実際にはなぜか見えない。

結局のところ、createGroup()ではLI要素に対してオブジェクトインスタンスを1個ずつ生成しているぐらいしかしていない。

createSimpleGroup()に戻る。

	createSimpleGroup : function(element, handle) {
		handle = handle ? handle : element
		var group = this.createGroup(element)
		group.setHandle(handle)
		group.transparentDrag()
		group.onTopWhileDragging()
		return group
	},

group.setHandle(handle)は、_ToolManDragGroup.prototypeにあり、

	setHandle : function(handle) {
		var events = ToolMan.events()

		handle.toolManDragGroup = this
		events.register(handle, 'mousedown', this._dragInit)
		handle.onmousedown = function() { return false }

		if (this.element != handle)
			events.unregister(this.element, 'mousedown', this._dragInit)
	},

eventsクラスはevents.jsで定義されている。events.register(handle, ‘mousedown’, this._dragInit)は、

	register : function(element, type, func) {
		if (element.addEventListener) {
			element.addEventListener(type, func, false)
		} else if (element.attachEvent) {
			if (!element._listeners) element._listeners = new Array()
			if (!element._listeners[type]) element._listeners[type] = new Array()
			var workaroundFunc = function() {
				func.apply(element, new Array())
			}
			element._listeners[type][func] = workaroundFunc
			element.attachEvent('on' + type, workaroundFunc)
		}
	},

で実装されている。elementにaddEventListenerかattachEventがあることを確認し、それぞれに処理を行っている。elementのtypeイベントのハンドラにfuncを指定するという感じだろう。

つまり、上のsetHandleではリスト(ul)に対して、 - mousedownで_ToolManDragGroup._dragInit() - onmousedownでは何もしないようにしている。 (ってこの2つは一緒じゃないのか?)

このコードが参照するHTML要素numericは、 -:text

<ul id="numeric">
  <li itemID="1">one</li>
  <li itemID="2">two</li>
  <li itemID="3">three</li>
</ul>

ToolManDragGroup ToolManDragSort

-:html # タイトル未定

最近はこんなことをするらしい。

if (typeof MyNamespace == "undefined") {
  MyNamespace = {};
}

JavaScript Consoleで見てみると、MyNamespace = {}は空のオブジェクトを作っているようだ。MyNamespaceの型はObject型になっている。 = {}の部分の意味が今ひとつちゃんと分からないのだが、多分、

MyNamespace = new Object()

と同じではないかと思う。

このObject型は、プロパティを設定することができるので、

MyObject.member = 1
MyObject.method = function() {
 ...
}
MyObject['member'] = 2

みたいな感じに使うことが出来る。

ECMA 262の4.3.3 Objectの項を見ると、

An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.

「オブジェクトとは、Object型のメンバーである。オブジェクトは並び順の無いプロパティのコレクションで、それぞれのプロパティはprimitive value, object, functionを持つことができる。オブジェクトのプロパティに格納された関数はメソッドと呼ばれる。」ということだ。

imported