此處程式是以 http://maps.gstatic.com/mapfiles/api-3/5/main.js 為對象。
Google maps api v3 的 event system 有兩種 event,一種是 Normal event,一種是 DOM event。Normal event 可以定義任何名稱的 event,而 DOM 的 event 就是 DOM 已定義的 event,而兩種 event 可以並用。範例如下:
HTML:
<span id="ts">test</span>
JavaScript:
var ts = document.getElementById("ts");
// register DOM event listener will also register normal event listener
google.maps.event.addDomListener(ts, "click", function() {
console.log("ts.click dom");
});
// Normal event listener will not be triggered by DOM event.
google.maps.event.addListener(ts, "click", function() {
console.log("ts.click");
});
// can register multiple handler on same event
google.maps.event.addListener(ts, "click", function() {
console.log("ts.click2");
});
// will trigger Normal event listener and DOM event Listener
google.maps.event.trigger(ts, "click");
執行結果為:
ts.click dom
ts.click
ts.click2
(而 mouse click 只觸發 ts.click dom 訊息)
可同時定義任意個數的 Normal event 和 DOM event。而 trigger 會同時觸發 Normal event 和 Dom event。
而 event 的 source code 有幾個相關的部分,第一是 event 本身:
var event = {};
// Adds the given listener function to the the given event name for the given object instance.
// Returns an identifier for this listener that can be used with eventRemoveListener().
//
// instance:Object, eventName:string, handler:Function, owner?:Object
// return EventListener
//
event.addListener = function(instance, eventName, handler, owner) {
// already add listener to instance's event listener chain
var e = new EventListener(instance, eventName, handler, 0, owner);
getInstance(EventListenerManager).addListener(e);
return e
};
其中 EventListener 和 EventListenerManager 的程式如下:
// instance, eventName, handler, 0, owner
function EventListener(instance, eventName, handler, domEventMethod, owner) {
nc(instance);
nc(typeof handler == "function");
this.u = instance;
this.Ga = eventName;
this.mb = handler;
this.ic = null;
this.Hi = domEventMethod;
this.ni = owner || null;
this._idx = -1;
getEventListenerChain(instance, eventName, true)["push"](this)
}
EventListener["prototype"].createOldStyleHandler = function() {
var a = this;
return this.ic = function(b) {
if (!b)
b = window.event;
if (b && !b.target)
try {
b.target = b.srcElement
} catch (c) {
}
var d = a.triggerHandler([b]);
if (b && "click" == b["type"]) {
var e = b.srcElement;
if (e && "A" == e["tagName"] && "javascript:void(0)" == e.href)
return false
}
return d
}
};
EventListener["prototype"].remove = function() {
if (this.u) {
switch (this.Hi) {
case 1 :
this.u.removeEventListener(this.Ga, this.mb, false);
break;
case 4 :
this.u.removeEventListener(this.Ga, this.mb, true);
break;
case 2 :
this.u.detachEvent("on" + this.Ga, this.ic);
break;
case 3 :
this.u["on" + this.Ga] = null;
break
}
for (var a = getEventListenerChain(this.u, this.Ga), b = 0, c = 0; c < getLength(a); ++c)
if (a[c] === this) {
a.splice(c--, 1);
b++
}
this.ic = this.mb = this.u = null
}
};
EventListener["prototype"].isOwner = function(a) {
return this.ni === a
};
EventListener["prototype"].triggerHandler = function(a) {
if (this.u)
return this.mb["apply"](this.u, a)
};
function EventListenerManager() {
this.I = []
}
EventListenerManager["prototype"].removeListener = function(listener) {
var b = listener._idx;
if (!(b < 0)) {
var c = this.I.pop();
if (b < this.I["length"]) {
this.I[b] = c;
c._idx = b
}
listener._idx = -1
}
};
EventListenerManager["prototype"].addListener = function(listener) {
this.I["push"](listener);
listener._idx = this.I["length"] - 1
};
EventListenerManager["prototype"].clear = function() {
array_foreach(this.I, function(a) {
a._idx = -1
});
setLength(this.I, 0)
};
可以看得出來,Normal Event 就是在 instance 下找個地方把 handler 收集起來,以便之後 trigger 時呼叫。其中 remove 是要配合之後 DOM event 的 remove 做法。
remove 也很容易理解:
// Removes the given listener, which should have been returned by eventAddListener above.
// listener:EventListener
event.removeListener = function(listener) {
listener["remove"]();
getInstance(EventListenerManager)["removeListener"](listener)
};
// Removes all listeners for the given event for the given instance.
// instance:Object, eventName:string, owner?:Object
event.clearListeners = function(instance, eventName, owner) {
array_foreach(cloneEventListenerChain(instance, eventName), function(d) {
if (!owner || d.isOwner(owner)) {
d["remove"]();
getInstance(EventListenerManager)["removeListener"](d)
}
})
};
// Removes all listeners for all events for the given instance.
// instance:Object, owner?:Object
event.clearInstanceListeners = function(instance, owner) {
array_foreach(cloneEventListenerChain(instance), function(c) {
if (!owner || c.isOwner(owner)) {
c["remove"]();
getInstance(EventListenerManager)["removeListener"](c)
}
})
};
event.clearAllEventListener = function() {
for (var a = [], b = getInstance(EventListenerManager).I, c = 0, d = getLength(b); c < d; ++c) {
var e = b[c], g = e.u;
if (!g.__tag__) {
g.__tag__ = true;
a["push"](g)
}
e["remove"]()
}
for (c = 0; c < getLength(a); ++c) {
g = a[c];
if (g.__tag__)
try {
delete g.__tag__;
delete g.__e_
} catch (h) {
g.__tag__ = false;
g.__e_ = null
}
}
getInstance(EventListenerManager).clear()
};
值得看到的是 getInstance 是用簡單的手法做 Singleton pattern。
function getInstance(a) {
if (!a.u)
a.u = new a;
return a.u
}
幾個 tool function:
// instance, eventName
function cloneEventListenerChain(instance, eventName) {
var c = [], d = instance.__e_;
if (d)
if (eventName)
d[eventName] && pushAll(c, d[eventName]);
else
foreach(d, function(e, g) {
pushAll(c, g)
});
return c
}
// instance, eventName, true
function getEventListenerChain(instance, eventName, create) {
var d = null, e = instance.__e_;
if (e) {
d = e[eventName];
if (!d) {
d = [];
if (create)
e[eventName] = d
}
} else {
d = [];
if (create) {
instance.__e_ = {};
instance.__e_[eventName] = d
}
}
return d
}
trigger 也很容易理解,就是找出註冊的 handler 然後一一呼叫就行了。
// Triggers the given event. All arguments after eventName are passed as arguments to the listeners.
// instance:Object, eventName:string, var_args:*
event.trigger = function(instance, eventName) {
var c = dc(arguments, 2);
array_foreach(cloneEventListenerChain(instance, eventName), function(d) {
d.triggerHandler(c)
})
};
DOM event 因為要支搜多種不同 browser 的 event 系統,所以需要做一些判斷。
// Cross browser event handler registration.
// This listener is removed by calling eventRemoveListener(handle) for the handle that is returned by this function.
// instance:Object, eventName:string, handler:Function, owner?:Object
// return EventListener
event.addDomListener = function(instance, eventName, handler, owner) {
var e;
if (instance.addEventListener) {
var g = false;
if (eventName == "focusin") {
eventName = "focus";
g = true
} else if (eventName == "focusout") {
eventName = "blur";
g = true
}
var h = g ? 4 : 1;
instance.addEventListener(eventName, handler, g);
e = new EventListener(instance, eventName, handler, h, owner)
} else if (instance.attachEvent) {
e = new EventListener(instance, eventName, handler, 2, owner);
instance.attachEvent("on" + eventName, e.createOldStyleHandler())
} else {
instance["on" + eventName] = handler;
e = new EventListener(instance, eventName, handler, 3, owner)
}
if (instance != window || eventName != "unload")
getInstance(EventListenerManager).addListener(e);
return e
};
也提供一些 forward 的手法方便使用。
event.forwardDomListener = function(instance, eventName, target, handler, owner) {
var g = xc(target, handler);
return event["addDomListener"](instance, eventName, g, owner)
};
function xc(a, b) {
nc(b);
return function(c) {
return b["call"](a, c, this)
}
}
event.forwardListener = function(instance, eventName, target, handler, owner) {
nc(handler);
return event["addListener"](instance, eventName, createCaller(target, handler), owner)
};
event.addOnetimeListener = function(instance, eventName, handler) {
var d = event["addListener"](instance, eventName, function() {
handler["apply"](instance, arguments);
event["removeListener"](d)
});
return d
};
event.forward = function(instance, eventName, target) {
return event["addListener"](instance, eventName, yc(eventName, target))
};
event.forwardDom = function(instance, eventName, target, owner) {
return event["addDomListener"](instance, eventName, yc(eventName, target, true), owner)
};
function yc(eventName, instance, isDom) {
return function(d) {
var e = [instance, eventName];
pushAll(e, arguments);
event["trigger"]["apply"](this, e);
if (isDom) {
var g = d || window.event;
g.cancelBubble = true;
g["stopPropagation"] && g["stopPropagation"]()
}
}
}
event 系統並沒有太複雜的做法,但卻是整個系統都會使用到的工具。