本次升级借鉴了jQuery事件模块的许多代码,可谓是jQuery事件模块的改良版。
与原先一样,拆分为两块,event模块是支持新一代的浏览器的,如IE9,chrome5+, opera10+,safari5+;event_fix是对付IE678。
拆分后的好处,在标准浏览器中,我们就不要加载这么多代码,跑这么多注定要跳过的分支,有效地提升性能。
拆分后就有利于我对标准浏览器有一个新的了解,发现firefox成为最拖后腿的一位。它在滚轮事件,focusin, focusout的迟迟不合作,让我们不得不奠出eventSupport等利器。webkit系还需要模拟mouseenter, mouseleave事件。由于标准浏览器的原生属性不能被覆盖,比如我们用mouseover来冒充mouseenter,那么我们还得将它外包一层,这工作为$.Event来做。它本来就是用于摒蔽事件对象在各浏览器的差异性的,让IE也拥有W3C的调用接口。因此$.Event还是不能移到event_fix模块。
在jQuery中还有一个simulate方法,用于让事件对象伪装成另一类事件,根据最后一个参数在本层或整个DOM树中传播。但通过研读源码发现,它的最后参数总是true,并且它也多用于修复IE的事件派发,只有一处是用于FF。因此我把它放到event_fix中去了。
在$.event.fix方法中,我发现jQuery对原事件对象的属性复制是限死的,规定好了某些属性要被复制,因此伪事件对象在一些场合还是要访问originalEvent来干活。这里我做了一些改良。并且针对鼠标事件与键盘事件这两类事件的大补丁,我也分配好它们的归属。一个目标,减少无效的分支判定。jQuery在dispatch这个方法中实在做了许多判定了,因此跑得很慢,这会在一些持续触发的事件,如scroll, resize, mousemove等非常吃力。
即使在event模块中,事件系统还是要对一些事件进行特殊处理,分别是load, focus, blur, click, beforeunload。
再来看event_fix,只要是处理IE的事件代理,change与submit,还有就是事件对象对标准的跟随。这补丁模块总归要入土的,但现在它对大陆人来说还是必不可少。
事件模块是使用wrap方式进行,完全伪装原短对象在DOM的触发行为。许多神一样的代码是jQuery团队写的,偶只是照搬。另一些奇技淫巧虽然是我自创的,但给出足够的链接希望你们能看得懂。这一个框架最重要也是最复杂的一部分,许多框架的事件系统能搞成这样具够扩展性也极让人困惑。
event.js
//=========================================
// 事件系统 v9
//==========================================
define(
"event"
, top.dispatchEvent ? [
"$node"
] : [
"$event_fix"
],
function
($) {
var
facade = $.event || ($.event = {
//对某种事件类型进行特殊处理
special: {},
//对Mouse事件这一大类事件类型的事件对象进行特殊处理
fixMouse:
function
(event, real) {
if
(event.type ===
"mousewheel"
) {
//处理滚轮事件
if
(
"wheelDelta"
in
real) {
//统一为±120,其中正数表示为向上滚动,负数表示向下滚动
var
delta = real.wheelDelta
//opera 9x系列的滚动方向与IE保持一致,10后修正
if
(window.opera && opera.version() < 10) delta = -delta;
event.wheelDelta = Math.round(delta);
//修正safari的浮点 bug
}
else
if
(
"detail"
in
real) {
event.wheelDelta = -real.detail * 40;
//修正FF的detail 为更大众化的wheelDelta
}
}
}
}),
eventHooks = facade.special,
rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
rtypenamespace = /^([^.]*)(?:\.(.+)|)$/,
mouseEvents =
"contextmenu,click,dblclick,mouseout,mouseover,mouseenter,mouseleave,mousemove,mousedown,mouseup,mousewheel,"
,
eventMap = $.oneObject(mouseEvents,
"Mouse"
),
types = mouseEvents +
",keypress,keydown,keyup,"
+
"blur,focus,focusin,focusout,"
+
"abort,error,load,unload,resize,scroll,change,input,select,reset,submit"
//input
$.eventSupport =
function
(eventName, el) {
el = el || document.createElement(
"div"
);
eventName =
"on"
+ eventName;
var
ret = eventName
in
el;
if
(el.setAttribute && !ret) {
el.setAttribute(eventName,
""
);
ret =
typeof
el[eventName] ===
"function"
;
el.removeAttribute(eventName);
}
el =
null
;
return
ret;
};
function
Event(src, props) {
if
(!(
this
instanceof
$.Event)) {
return
new
Event(src, props);
}
this
.originalEvent = {};
//保存原生事件对象
if
(src && src.type) {
this
.originalEvent = src;
//重写
this
.type = src.type;
}
else
{
this
.type = src;
}
this
.defaultPrevented =
false
;
if
(props) {
$.mix(
this
, props);
}
this
.timeStamp =
new
Date - 0;
};
Event.prototype = {
toString:
function
() {
return
"[object Event]"
},
preventDefault:
function
() {
//阻止默认行为
this
.defaultPrevented =
true
;
var
e =
this
.originalEvent
if
(e && e.preventDefault) {
e.preventDefault();
}
e.returnValue =
false
;
return
this
;
},
stopPropagation:
function
() {
//阻止事件在DOM树中的传播
var
e =
this
.originalEvent
if
(e && e.stopPropagation) {
e.stopPropagation();
e.cancelBubble =
this
.propagationStopped =
true
;
return
this
;
},
stopImmediatePropagation:
function
() {
//阻止事件在一个元素的同种事件的回调中传播
this
.isImmediatePropagationStopped =
true
;
this
.stopPropagation();
return
this
;
}
}
$.Event = Event;
$.mix(eventHooks, {
load: {
//此事件不能冒泡
noBubble:
true
},
click: {
//处理checkbox中的点击事件
trigger:
function
() {
if
(
this
.nodeName ==
"INPUT"
&&
this
.type ===
"checkbox"
&&
this
.click) {
this
.click();
return
false
;
}
}
},
focus: {
//IE9-在不能聚焦到隐藏元素上,强制触发此事件会抛错
trigger:
function
() {
if
(
this
!== document.activeElement &&
this
.focus) {
try
{
this
.focus();
return
false
;
}
catch
(e) {}
}
},
delegateType:
"focusin"
},
blur: {
trigger:
function
() {
//blur事件的派发使用原生方法实现
if
(
this
=== document.activeElement &&
this
.blur) {
this
.blur();
return
false
;
}
},
delegateType:
"focusout"
},
beforeunload: {
postDispatch:
function
(event) {
if
(event.result !== void 0) {
event.originalEvent.returnValue = event.result;
}
}
}
});
$.mix(facade, {
//addEventListner API的支持情况:chrome 1+ FF1.6+ IE9+ opera 7+ safari 1+;
add:
function
(elem, hash) {
var
elemData = $._data(elem),
//取得对应的缓存体
types = hash.type,
//原有的事件类型,可能是复数个
selector = hash.selector,
//是否使用事件代理
handler = hash.handler;
//回调函数
if
(elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler) {
return
;
}
hash.uniqueNumber = $.getUid(handler);
//确保hash.uuid与fn.uuid一致
var
events = elemData.events || (elemData.events = []),
eventHandle = elemData.handle;
if
(!eventHandle) {
elemData.handle = eventHandle =
function
(e) {
return
typeof
$ !==
"undefined"
&& (!e || facade.triggered !== e.type) ? facade.dispatch.apply(eventHandle.elem, arguments) : void 0;
};
eventHandle.elem = elem;
//由于IE的attachEvent回调中的this不指向绑定元素,需要强制缓存它
}
types.replace($.rword,
function
(t) {
var
tns = rtypenamespace.exec(t) || [],
type = tns[1];
var
namespaces = (tns[2] ||
""
).split(
"."
).sort();
// 看需不需要特殊处理
var
hook = eventHooks[type] || {};
// 事件代理与事件绑定可以使用不同的冒充事件
type = (selector ? hook.delegateType : hook.bindType) || type;
hook = eventHooks[type] || {};
var
handleObj = $.mix({}, hash, {
type: type,
origType: tns[1],
namespace: namespaces.join(
"."
)
});
var
handlers = events[type];
//初始化事件列队
if
(!handlers) {
handlers = events[type] = [];
handlers.delegateCount = 0;
if
(!hook.setup || hook.setup.call(elem, namespaces, eventHandle) ===
false
) {
if
($[
"@bind"
]
in
elem) {
$.bind(elem, type, eventHandle)
}
}
}
if
(hook.add) {
hook.add.call(elem, handleObj);
}
//先处理用事件代理的回调,再处理用普通方式绑定的回调
if
(selector) {
handlers.splice(handlers.delegateCount++, 0, handleObj);
}
else
{
handlers.push(handleObj);
}
//用于优化fire方法
facade.global[type] =
true
;
})
//防止IE内在泄漏
elem =
null
;
},
//用于优化事件派发
global: {},
//移除目标元素绑定的回调
remove:
function
(elem, hash) {
var
elemData = $._data(elem),
events, origType
if
(!(events = elemData.events))
return
;
var
types = hash.type ||
""
,
selector = hash.selector,
handler = hash.handler;
types.replace($.rword,
function
(t) {
var
tns = rtypenamespace.exec(t) || [],
type = origType = tns[1],
namespaces = tns[2];
//只传入命名空间,不传入事件类型,则尝试遍历所有事件类型
if
(!type) {
for
(type
in
events) {
facade.unbind(elem, $.mix({}, hash, {
type: type + t
}));
}
return
}
var
hook = eventHooks[type] || {};
type = (selector ? hook.delegateType : hook.bindType) || type;
var
handlers = events[type] || [];
var
origCount = handlers.length;
namespaces = namespaces ?
new
RegExp(
"(^|\\.)"
+ namespaces.split(
"."
).sort().join(
"\\.(?:.*\\.|)"
) +
"(\\.|$)"
) :
null
;
for
(
var
j = 0, handleObj; j < handlers.length; j++) {
handleObj = handlers[j];
//如果事件类型相同,回调相同,命名空间相同,选择器相同则移除此handleObj
if
((origType === handleObj.origType) && (!handler || handler.uniqueNumber === handleObj.uniqueNumber) && (!namespaces || namespaces.test(handleObj.namespace)) && (!selector || selector === handleObj.selector || selector ===
"**"
&& handleObj.selector)) {
handlers.splice(j--, 1);
if
(handleObj.selector) {
handlers.delegateCount--;
}
if
(hook.remove) {
hook.remove.call(elem, handleObj);
}
}
}
if
(handlers.length === 0 && origCount !== handlers.length) {
if
(!hook.teardown || hook.teardown.call(elem, namespaces, elemData.handle) ===
false
) {
if
($[
"@bind"
]
in
elem) {
$.unbind(elem, type, elemData.handle)
}
}
delete
events[type];
}
})
if
($.isEmptyObject(events)) {
delete
elemData.handle;
$._removeData(elem,
"events"
);
//这里会尝试移除缓存体
}
},
//通过传入事件类型或事件对象,触发事件回调,在整个DOM树中执行
trigger:
function
(event) {
var
elem =
this
;
//跳过文本节点与注释节点,主要是照顾旧式IE
if
(elem && (elem.nodeType === 3 || elem.nodeType === 8)) {
return
;
}
var
i, cur, old, ontype, handle, eventPath, bubbleType, type = event.type || event,
namespaces = event.namespace ? event.namespace.split(
"."
) : [];
// focus/blur morphs to focusin/out; ensure we're not firing them right now
if
(rfocusMorph.test(type + facade.triggered)) {
return
;
}
if
(type.indexOf(
"."
) >= 0) {
//分解出命名空间
namespaces = type.split(
"."
);
type = namespaces.shift();
namespaces.sort();
}
//如果从来没有绑定过此种事件,也不用继续执行了
if
(!elem && !facade.global[type]) {
return
;
}
// Caller can pass in an Event, Object, or just an event type string
event =
typeof
event ===
"object"
?
// 如果是$.Event实例
event.originalEvent ? event :
// Object literal
new
$.Event(type, event) :
// Just the event type (string)
new
$.Event(type);
event.type = type;
event.isTrigger =
true
;
event.namespace = namespaces.join(
"."
);
event.namespace_re = event.namespace ?
new
RegExp(
"(^|\\.)"
+ namespaces.join(
"\\.(?:.*\\.|)"
) +
"(\\.|$)"
) :
null
;
ontype = type.indexOf(
":"
) < 0 ?
"on"
+ type :
""
;
//清除result,方便重用
event.result = void 0;
if
(!event.target) {
event.target = elem;
}
//取得额外的参数
var
data = $.slice(arguments);
data[0] = event;
//判定是否需要用到事件冒充
var
hook = eventHooks[type] || {};
if
(hook.trigger && hook.trigger.apply(elem, data) ===
false
) {
return
;
}
//铺设往上冒泡的路径,每小段都包括处理对象与事件类型
eventPath = [
[elem, hook.bindType || type]
];
if
(!hook.noBubble && !$.type(elem,
"Window"
)) {
bubbleType = hook.delegateType || type;
cur = rfocusMorph.test(bubbleType + type) ? elem : elem.parentNode;
for
(old = elem; cur; cur = cur.parentNode) {
eventPath.push([cur, bubbleType]);
old = cur;
}
//一直冒泡到window
if
(old === (elem.ownerDocument || document)) {
eventPath.push([old.defaultView || old.parentWindow || window, bubbleType]);
}
}
//沿着之前铺好的路触发事件
for
(i = 0; i < eventPath.length && !event.propagationStopped; i++) {
cur = eventPath[i][0];
event.type = eventPath[i][1];
handle = ($._data(cur,
"events"
) || {})[event.type] && $._data(cur,
"handle"
);
if
(handle) {
handle.apply(cur, data);
}
//处理直接写在标签中的内联事件或DOM0事件
handle = ontype && cur[ontype];
if
(handle && handle.apply && handle.apply(cur, data) ===
false
) {
event.preventDefault();
}
}
event.type = type;
//如果没有阻止默认行为
if
(!event.defaultPrevented) {
if
((!hook._default || hook._default.apply(elem.ownerDocument, data) ===
false
) && !(type ===
"click"
&& elem.nodeName ==
"A"
)) {
if
(ontype && $.isFunction(elem[type]) && elem.nodeType) {
old = elem[ontype];
if
(old) {
elem[ontype] =
null
;
}
//防止二次trigger,elem.click会再次触发addEventListener中绑定的事件
facade.triggered = type;
try
{
//IE6-8在触发隐藏元素的focus/blur事件时会抛出异常
elem[type]();
}
catch
(e) {}
delete
facade.triggered;
if
(old) {
elem[ontype] = old;
}
}
}
}
return
event.result;
},
//执行用户回调,只在当前元素中执行
dispatch:
function
(e) {
//如果不存在事件回调就没有必要继续进行下去
var
eventType = e.type,
handlers = (($._data(
this
,
"events"
) || {})[eventType] || [])
if
(!handlers.length) {
return
;
}
//摒蔽事件对象在各浏览器下的差异性
var
event = $.event.fix(e),
delegateCount = handlers.delegateCount,
args = $.slice(arguments),
hook = eventHooks[eventType] || {},
handlerQueue = [],
ret, selMatch, matched, matches, handleObj, sel
//重置第一个参数
args[0] = event;
event.delegateTarget =
this
;
// 经典的AOP模式
if
(hook.preDispatch && hook.preDispatch.call(
this
, event) ===
false
) {
return
;
}
//收集阶段
//如果使用了事件代理,则先执行事件代理的回调, FF的右键会触发点击事件,与标签不符
if
(delegateCount && !(event.button && eventType ===
"click"
)) {
for
(
var
cur = event.target; cur !=
this
; cur = cur.parentNode ||
this
) {
//disabled元素不能触发点击事件
if
(cur.disabled !==
true
|| eventType !==
"click"
) {
selMatch = {};
matches = [];
for
(
var
i = 0; i < delegateCount; i++) {
handleObj = handlers[i];
sel = handleObj.selector;
//判定目标元素(this)的孩子(cur)是否匹配(sel)
if
(selMatch[sel] === void 0) {
selMatch[sel] = $(sel,
this
).index(cur) >= 0
}
if
(selMatch[sel]) {
matches.push(handleObj);
}
}
if
(matches.length) {
handlerQueue.push({
elem: cur,
matches: matches
});
}
}
}
}
// 这是事件绑定的回调
if
(handlers.length > delegateCount) {
handlerQueue.push({
elem:
this
,
matches: handlers.slice(delegateCount)
});
}
// 如果没有阻止事件传播,则执行它们
for
(i = 0; i < handlerQueue.length && !event.propagationStopped; i++) {
matched = handlerQueue[i];
event.currentTarget = matched.elem;
for
(
var
j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped; j++) {
handleObj = matched.matches[j];
//namespace,.namespace_re属性只出现在trigger方法中
if
(!event.namespace || event.namespace_re && event.namespace_re.test(handleObj.namespace)) {
event.data = handleObj.data;
event.handleObj = handleObj;
ret = ((eventHooks[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args);
handleObj.times--;
if
(handleObj.times === 0) {
//如果有次数限制并到用光所有次数,则移除它
facade.unbind(matched.elem, handleObj)
}
if
(ret !== void 0) {
event.result = ret;
if
(ret ===
false
) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}
if
(hook.postDispatch) {
hook.postDispatch.call(
this
, event);
}
return
event.result;
},
//修正事件对象,摒蔽差异性
fix:
function
(event) {
if
(!event.originalEvent) {
var
real = event;
event = $.Event(real);
//复制真实事件对象的成员
for
(
var
p
in
real) {
if
(!(p
in
event)) {
event[p] = real[p]
}
}
//如果不存在target属性,为它添加一个
if
(!event.target) {
event.target = event.srcElement || document;
}
//safari的事件源对象可能为文本节点,应代入其父节点
if
(event.target.nodeType === 3) {
event.target = event.target.parentNode;
}
event.metaKey = !! event.ctrlKey;
// 处理IE678的组合键
var
callback = facade[
"fix"
+ eventMap[event.type]]
if
(
typeof
callback ==
"function"
) {
callback(event, real)
}
}
return
event;
}
});
facade.bind = facade.add;
facade.unbind = facade.remove;
//以下是用户使用的API
$.implement({
hover:
function
(fnIn, fnOut) {
return
this
.mouseenter(fnIn).mouseleave(fnOut || fnIn);
},
delegate:
function
(selector, types, fn, times) {
return
this
.on(types, selector, fn, times);
},
live:
function
(types, fn, times) {
$.log(
"$.fn.live() is deprecated"
)
$(
this
.ownerDocument).on(types,
this
.selector, fn, times);
return
this
;
},
one:
function
(types, fn) {
return
this
.on(types, fn, 1);
},
undelegate:
function
(selector, types, fn) {
/*顺序不能乱*/
return
arguments.length == 1 ?
this
.off(selector,
"**"
) :
this
.off(types, fn, selector);
},
die:
function
(types, fn) {
$.log(
"$.fn.die() is deprecated"
)
$(
this
.ownerDocument).off(types, fn,
this
.selector ||
"**"
, fn);
return
this
;
},
fire:
function
() {
var
args = arguments;
return
this
.each(
function
() {
facade.trigger.apply(
this
, args);
});
}
});
//这个迭代器产生四个重要的事件绑定API on off bind unbind
var
rtypes = /^[a-z0-9_\-\.\s\,]+$/i
"on_bind,off_unbind"
.replace($.rmapper,
function
(_, method, mapper) {
$.fn[method] =
function
(types, selector, fn) {
if
(
typeof
types ===
"object"
) {
for
(
var
type
in
types) {
$.fn[method](
this
, type, selector, types[type], fn);
}
return
this
;
}
var
hash = {};
for
(
var
i = 0; i < arguments.length; i++) {
var
el = arguments[i];
if
(
typeof
el ==
"number"
) {
hash.times = el;
}
else
if
(
typeof
el ==
"function"
) {
hash.handler = el
}
if
(
typeof
el ===
"string"
) {
if
(hash.type !=
null
) {
hash.selector = el.trim();
}
else
{
hash.type = el.trim();
//只能为字母数字-_.空格
if
(!rtypes.test(hash.type)) {
throw
new
Error(
"事件类型格式不正确"
)
}
}
}
}
if
(!hash.type) {
throw
new
Error(
"必须指明事件类型"
)
}
if
(method ===
"on"
&& !hash.handler) {
throw
new
Error(
"必须指明事件回调"
)
}
hash.times = hash.times > 0 ? hash.times : Infinity;
return
this
.each(
function
() {
facade[mapper](
this
, hash);
});
}
$.fn[mapper] =
function
() {
// $.fn.bind $.fn.unbind
return
$.fn[method].apply(
this
, arguments);
}
});
types.replace($.rword,
function
(type) {
//这里产生以事件名命名的快捷方法
eventMap[type] = eventMap[type] || (/key/.test(type) ?
"Keyboard"
:
"HTML"
)
$.fn[type] =
function
(callback) {
return
callback ?
this
.bind(type, callback) :
this
.fire(type);
}
});
/* mouseenter/mouseleave/focusin/focusout已为标准事件,经测试IE5+,opera11,FF10+都支持它们
*/
if
(!+
"\v1"
|| !$.eventSupport(
"mouseenter"
)) {
//IE6789不能实现捕获与safari chrome不支持
"mouseenter_mouseover,mouseleave_mouseout"
.replace($.rmapper,
function
(_, type, fix) {
eventHooks[type] = {
delegateType: fix,
bindType: fix,
handle:
function
(event) {
var
ret, target =
this
,
related = event.relatedTarget,
handleObj = event.handleObj;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
if
(!related || (related !== target && !$.contains(target, related))) {
event.type = handleObj.origType;
ret = handleObj.handler.apply(
this
, arguments);
event.type = fix;
}
return
ret;
}
}
});
}
//现在只有firefox不支持focusin,focusout事件,并且它也不支持DOMFocusIn,DOMFocusOut,不能像DOMMouseScroll那样简单冒充,Firefox 17+
if
(!$.support.focusin) {
"focusin_focus,focusout_blur"
.replace($.rmapper,
function
(_, orig, fix) {
var
attaches = 0,
handler =
function
(event) {
event = facade.fix(event);
$.mix(event, {
type: orig,
isSimulated:
true
});
facade.trigger.call(event.target, event);
};
eventHooks[orig] = {
setup:
function
() {
if
(attaches++ === 0) {
document.addEventListener(fix, handler,
true
);
}
},
teardown:
function
() {
if
(--attaches === 0) {
document.removeEventListener(fix, handler,
true
);
}
}
};
});
}
try
{
//FF需要用DOMMouseScroll事件模拟mousewheel事件
document.createEvent(
"MouseScrollEvents"
);
eventHooks.mousewheel = {
bindType:
"DOMMouseScroll"
,
delegateType:
"DOMMouseScroll"
}
if
($.eventSupport(
"mousewheel"
)) {
delete
eventHooks.mousewheel;
}
}
catch
(e) {};
return
$;
})
event_fix.js
define(
"event_fix"
, !! document.dispatchEvent, [
"$node"
],
function
($) {
//模拟IE678的reset,submit,change的事件代理
var
rformElems = /^(?:input|select|textarea)$/i
var
facade = $.event = {
special: {},
fixMouse:
function
(event) {
var
doc = event.target.ownerDocument || document;
//safari与chrome下,滚动条,视窗相关的东西是放在body上
var
box = document.compatMode ==
"BackCompat"
? doc.body : doc.documentElement
event.pageX = event.clientX + (box.scrollLeft >> 0) - (box.clientLeft >> 0);
event.pageY = event.clientY + (box.scrollTop >> 0) - (box.clientTop >> 0);
//如果不存在relatedTarget属性,为它添加一个
if
(!event.relatedTarget && event.fromElement) {
//mouseover mouseout
event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
}
//标准浏览判定按下鼠标哪个键,左1中2右3
var
button = event.button
//IE event.button的意义 0:没有键被按下 1:按下左键 2:按下右键 3:左键与右键同时被按下 4:按下中键 5:左键与中键同时被按下 6:中键与右键同时被按下 7:三个键同时被按下
event.which = [0, 1, 3, 0, 2, 0, 0, 0][button];
//0现在代表没有意义
},
fixKeyboard:
function
(event) {
event.which = event.charCode !=
null
? event.charCode : event.keyCode;
}
};
var
special = facade.special
function
simulate(type, elem, event) {
event =
new
$.Event(event);
$.mix({
type: type,
isSimulated:
true
});
$.event.trigger.call(elem, event);
if
(event.defaultPrevented) {
event.preventDefault();
}
}
special.change = {
setup:
function
() {
if
(rformElems.test(
this
.nodeName)) {
// IE doesn't fire change on a check/radio until blur; trigger it on click
// after a propertychange. Eat the blur-change in special.change.handle.
// This still fires onchange a second time for check/radio after blur.
if
(
this
.type ===
"checkbox"
||
this
.type ===
"radio"
) {
$(
this
).bind(
"propertychange._change"
,
function
(event) {
if
(event.originalEvent.propertyName ===
"checked"
) {
this
._just_changed =
true
;
}
});
$(
this
).bind(
"click._change"
,
function
(event) {
if
(
this
._just_changed && !event.isTrigger) {
this
._just_changed =
false
;
}
// Allow triggered, simulated change events (#11500)
simulate(
"change"
,
this
, event);
});
}
return
false
;
}
// Delegated event; lazy-add a change handler on descendant inputs
$(
this
).bind(
"beforeactivate._change"
,
function
(e) {
var
elem = e.target;
if
(rformElems.test(elem.nodeName) && !$._data(elem,
"_change_attached"
)) {
$(elem).bind(
"change._change"
,
function
(event) {
if
(
this
.parentNode && !event.isSimulated && !event.isTrigger) {
simulate(
"change"
,
this
.parentNode, event);
}
$._data(elem,
"_change_attached"
,
true
);
})
}
});
},
handle:
function
(event) {
var
elem = event.target;
// Swallow native change events from checkbox/radio, we already triggered them above
if
(
this
!== elem || event.isSimulated || event.isTrigger || (elem.type !==
"radio"
&& elem.type !==
"checkbox"
)) {
return
event.handleObj.handler.apply(
this
, arguments);
}
},
teardown:
function
() {
facade.remove(
this
,
"._change"
);
return
!rformElems.test(
this
.nodeName);
}
}
special.submit = {
setup:
function
() {
// Only need this for delegated form submit events
if
(
this
.tagName ===
"FORM"
) {
return
false
;
}
// Lazy-add a submit handler when a descendant form may potentially be submitted
$(
this
).bind(
"click._submit keypress._submit"
,
function
(e) {
// Node name check avoids a VML-related crash in IE (#9807)
var
elem = e.target,
form = /input|button/i.test(elem.tagName) ? elem.form : undefined;
if
(form && !$._data(form,
"_submit_attached"
)) {
facade.bind(form, {
type:
"submit._submit"
,
callback:
function
(event) {
event._submit_bubble =
true
;
}
});
$._data(form,
"_submit_attached"
,
true
);
}
});
// return undefined since we don't need an event listener
},
postDispatch:
function
(event) {
// If form was submitted by the user, bubble the event up the tree
if
(event._submit_bubble) {
delete
event._submit_bubble;
if
(
this
.parentNode && !event.isTrigger) {
simulate(
"submit"
,
this
.parentNode, event);
}
}
},
teardown:
function
() {
if
(
this
.tagName ==
"FORM"
) {
return
false
;
}
facade.remove(
this
,
"._submit"
);
}
}
return
$;
})