can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
205 lines (167 loc) • 5.5 kB
JavaScript
/*
---
name: Element.Delegation
description: Extends the Element native object to include the delegate method for more efficient event management.
license: MIT-style license.
requires: [Element.Event]
provides: [Element.Delegation]
...
*/
(function(){
var eventListenerSupport = !!window.addEventListener;
Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
var bubbleUp = function(self, match, fn, event, target){
while (target && target != self){
if (match(target, event)) return fn.call(target, event, target);
target = document.id(target.parentNode);
}
};
var map = {
mouseenter: {
base: 'mouseover',
condition: Element.MouseenterCheck
},
mouseleave: {
base: 'mouseout',
condition: Element.MouseenterCheck
},
focus: {
base: 'focus' + (eventListenerSupport ? '' : 'in'),
capture: true
},
blur: {
base: eventListenerSupport ? 'blur' : 'focusout',
capture: true
}
};
/*<ltIE9>*/
var _key = '$delegation:';
var formObserver = function(type){
return {
base: 'focusin',
remove: function(self, uid){
var list = self.retrieve(_key + type + 'listeners', {})[uid];
if (list && list.forms) for (var i = list.forms.length; i--;){
// the form may have been destroyed, so it won't have the
// removeEvent method anymore. In that case the event was
// removed as well.
if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]);
}
},
listen: function(self, match, fn, event, target, uid){
var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
if (!form) return;
var listeners = self.retrieve(_key + type + 'listeners', {}),
listener = listeners[uid] || {forms: [], fns: []},
forms = listener.forms, fns = listener.fns;
if (forms.indexOf(form) != -1) return;
forms.push(form);
var _fn = function(event){
bubbleUp(self, match, fn, event, target);
};
form.addEvent(type, _fn);
fns.push(_fn);
listeners[uid] = listener;
self.store(_key + type + 'listeners', listeners);
}
};
};
var inputObserver = function(type){
return {
base: 'focusin',
listen: function(self, match, fn, event, target){
var events = {blur: function(){
this.removeEvents(events);
}};
events[type] = function(event){
bubbleUp(self, match, fn, event, target);
};
event.target.addEvents(events);
}
};
};
if (!eventListenerSupport) Object.append(map, {
submit: formObserver('submit'),
reset: formObserver('reset'),
change: inputObserver('change'),
select: inputObserver('select')
});
/*</ltIE9>*/
var proto = Element.prototype,
addEvent = proto.addEvent,
removeEvent = proto.removeEvent;
var relay = function(old, method){
return function(type, fn, useCapture){
if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
var parsed = Slick.parse(type).expressions[0][0];
if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
var newType = parsed.tag;
parsed.pseudos.slice(1).each(function(pseudo){
newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
});
old.call(this, type, fn);
return method.call(this, newType, parsed.pseudos[0].value, fn);
};
};
var delegation = {
addEvent: function(type, match, fn){
var storage = this.retrieve('$delegates', {}), stored = storage[type];
if (stored) for (var _uid in stored){
if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
}
var _type = type, _match = match, _fn = fn, _map = map[type] || {};
type = _map.base || _type;
match = function(target){
return Slick.match(target, _match);
};
var elementEvent = Element.Events[_type];
if (_map.condition || elementEvent && elementEvent.condition){
var __match = match, condition = _map.condition || elementEvent.condition;
match = function(target, event){
return __match(target, event) && condition.call(target, event, type);
};
}
var self = this, uid = String.uniqueID();
var delegator = _map.listen ? function(event, target){
if (!target && event && event.target) target = event.target;
if (target) _map.listen(self, match, fn, event, target, uid);
} : function(event, target){
if (!target && event && event.target) target = event.target;
if (target) bubbleUp(self, match, fn, event, target);
};
if (!stored) stored = {};
stored[uid] = {
match: _match,
fn: _fn,
delegator: delegator
};
storage[_type] = stored;
return addEvent.call(this, type, delegator, _map.capture);
},
removeEvent: function(type, match, fn, _uid){
var storage = this.retrieve('$delegates', {}), stored = storage[type];
if (!stored) return this;
if (_uid){
var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
type = _map.base || _type;
if (_map.remove) _map.remove(this, _uid);
delete stored[_uid];
storage[_type] = stored;
return removeEvent.call(this, type, delegator, _map.capture);
}
var __uid, s;
if (fn) for (__uid in stored){
s = stored[__uid];
if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
} else for (__uid in stored){
s = stored[__uid];
if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
}
return this;
}
};
[Element, Window, Document].invoke('implement', {
addEvent: relay(addEvent, delegation.addEvent),
removeEvent: relay(removeEvent, delegation.removeEvent)
});
})();