libdom
Version:
Lean Browser Library for typical DOM operations
524 lines (407 loc) • 12.4 kB
JavaScript
;
import {
string,
method,
contains,
object,
middleware
} from "libcore";
import { ERROR } from "./string.js";
import INFO from "./detect.js";
import { get as getModule } from "./chain.js";
var EVENTS = null,
PAGE_UNLOADED = false,
MIDDLEWARE = middleware('libdom.event'),
IE_CUSTOM_EVENTS = {},
ERROR_OBSERVABLE_NO_SUPPORT = ERROR[1131],
ERROR_INVALID_TYPE = ERROR[1132],
ERROR_INVALID_HANDLER = ERROR[1133],
IE_ON = 'on',
IE_BUBBLE_EVENT = 'beforeupdate',
IE_NO_BUBBLE_EVENT = 'propertychange';
var RESOLVE, LISTEN, UNLISTEN, DISPATCH, EVENT_INFO, IS_CAPABLE, SUBJECT;
function createUnlistener(event) {
var destroyed = false;
function destroy() {
var head, tail;
if (!destroyed) {
destroyed = true;
UNLISTEN(event[0], event[1], event[4]);
head = event.head;
tail = event.tail;
if (head) {
head.tail = tail;
}
if (tail) {
tail.head = head;
}
if (event === EVENTS) {
EVENTS = tail || head;
}
event[0] = null;
event.splice(0, 4);
delete event.unlisten;
delete event.head;
delete event.tail;
event = head = tail = null;
}
}
return destroy;
}
function filter(argLen, observable, type, handler, context) {
var last = EVENTS,
found = [],
len = 0,
HAS_OBSERVABLE = 0,
HAS_TYPE = 0,
HAS_HANDLER = 0,
HAS_CONTEXT = 0;
switch (true) {
case argLen > 3: HAS_CONTEXT = 1;
/* fall through */
case argLen > 2: HAS_HANDLER = 1;
/* fall through */
case argLen > 1: HAS_TYPE = 1;
/* fall through */
case argLen > 0: HAS_OBSERVABLE = 1;
}
for (; last; last = last.head) {
if ((HAS_OBSERVABLE && last[0] !== observable) ||
(HAS_TYPE && last[1] !== type) ||
(HAS_HANDLER && last[2] !== handler) ||
(HAS_CONTEXT && last[3] !== context)) {
continue;
}
found[len++] = last;
}
return found;
}
/**
* w3c events
*/
function w3cListen(observable, type, handler, context) {
var listener = w3cCreateHandler(handler, context);
observable.addEventListener(type, listener, false);
return [observable, type, handler, context, listener];
}
function w3cUnlisten(observable, type, listener) {
observable.removeEventListener(type, listener, false);
}
function w3cDispatch(observable, type, properties) {
var hasOwn = contains,
event = global.document.createEvent("Event");
var name;
event.initEvent(type,
'bubbles' in properties && properties.bubbles === true,
'cancelable' in properties && properties.cancelable !== false);
for (name in properties) {
if (hasOwn(properties, name) && !(name in event)) {
event[name] = properties[name];
}
}
observable.dispatchEvent(event);
return event;
}
function w3cObservable(observable) {
var isFunction = method;
return observable && typeof observable === 'object' &&
isFunction(observable.addEventListener) &&
isFunction(observable.removeEventListener) &&
isFunction(observable.dispatchEvent) ?
observable : false;
}
function w3cCreateHandler(handler, context) {
function onEvent(event) {
MIDDLEWARE.run('dispatch', [event.type, event]);
return handler.call(context, event);
}
return onEvent;
}
/**
* ie events
*/
function ieListen(observable, type, handler, context) {
var on = IE_ON;
var listener;
// listen to bubble
if (ieTestCustomEvent(observable, type)) {
listener = ieCreateCustomHandler(type, handler, context);
observable.attachEvent(on + IE_BUBBLE_EVENT, listener);
observable.attachEvent(on + IE_NO_BUBBLE_EVENT, listener);
}
else {
listener = ieCreateHandler(handler, context);
observable.attachEvent(on + type, listener);
}
return [observable, type, handler, context, listener];
}
function ieUnlisten(observable, type, listener) {
var on = IE_ON;
if (listener.customType) {
observable.detachEvent(on + IE_BUBBLE_EVENT, listener);
observable.detachEvent(on + IE_NO_BUBBLE_EVENT, listener);
}
else {
observable.detachEvent(on + type, listener);
}
}
function ieDispatch(observable, type, properties) {
var hasOwn = contains,
event = global.document.createEventObject();
var name;
for (name in properties) {
if (hasOwn(properties, name) && !(name in event)) {
event[name] = properties[name];
}
}
if (ieTestCustomEvent(observable, type)) {
event.customType = type;
type = properties.bubbles === true ?
IE_BUBBLE_EVENT : IE_NO_BUBBLE_EVENT;
}
name = IE_ON + type;
observable.fireEvent(name, event);
// set to not cancel if not cancelable
if (properties.cancelable === false) {
event.returnValue = true;
}
return event;
}
function ieObservable(observable) {
if (observable) {
observable = observable.window ? observable.self : observable;
if (observable.attachEvent && observable.detachEvent) {
return observable;
}
}
return false;
}
function ieCreateHandler(handler, context) {
function onEvent() {
var event = global.event;
iePolyfillEvent(event);
MIDDLEWARE.run('dispatch', [event.type, event]);
return handler.call(context, event);
}
return onEvent;
}
function ieCreateCustomHandler(type, handler, context) {
function onEvent() {
var event = global.event;
iePolyfillEvent(event);
if (event.customType === type) {
MIDDLEWARE.run('dispatch', [type, event]);
event.type = type;
return handler.call(context, event);
}
}
onEvent.customType = true;
return onEvent;
}
function iePreventDefault() {
/* jshint validthis:true */
this.returnValue = false;
}
function ieStopPropagation() {
/* jshint validthis:true */
this.cancelBubble = true;
}
function iePolyfillEvent(eventObject) {
eventObject.target = eventObject.target || eventObject.srcElement;
if (!('preventDefault' in eventObject)) {
eventObject.preventDefault = iePreventDefault;
}
if (!('stopPropagation' in eventObject)) {
eventObject.stopPropagation = ieStopPropagation;
}
}
function ieTestCustomEvent(observable, type) {
var supported = false,
list = IE_CUSTOM_EVENTS;
var element, access, ontype;
if (observable.nodeType === 9) {
observable = observable.documentElement;
}
if (observable.nodeType === 1) {
// dont do another test
access = observable.tagName + ':' + type;
if (access in list) {
return list[access];
}
ontype = IE_ON + type;
element = observable.cloneNode(false);
supported = ontype in element;
if (!supported) {
element.setAttribute(ontype, 'return;');
supported = typeof element[ontype] === 'function';
}
element = null;
list[access] = !supported;
return !supported;
}
return false;
}
/**
* purge after page has unloaded
*/
function onBeforeUnload() {
if (!PAGE_UNLOADED) {
PAGE_UNLOADED = true;
// call middleware
MIDDLEWARE.run('global-destroy', []);
purge();
}
}
RESOLVE = LISTEN = UNLISTEN = DISPATCH = null;
/**
* Initialize
*/
EVENT_INFO = INFO && INFO.event;
if (EVENT_INFO) {
IS_CAPABLE = true;
switch (true) {
case EVENT_INFO.w3c:
LISTEN = w3cListen;
UNLISTEN = w3cUnlisten;
DISPATCH = w3cDispatch;
RESOLVE = w3cObservable;
break;
case EVENT_INFO.ie:
LISTEN = ieListen;
UNLISTEN = ieUnlisten;
DISPATCH = ieDispatch;
RESOLVE = ieObservable;
break;
default:
IS_CAPABLE = false;
}
// postprocess event handlers if platform is capable of L2 events
if (IS_CAPABLE) {
SUBJECT = global;
// register destructors
on(SUBJECT, 'beforeunload', onBeforeUnload);
on(SUBJECT, 'unload', onBeforeUnload);
SUBJECT = null;
}
}
export
function on(observable, type, handler, context) {
var last = EVENTS;
var current, args;
if (!string(type)) {
throw new Error(ERROR_INVALID_TYPE);
}
if (!method(handler)) {
throw new Error(ERROR_INVALID_HANDLER);
}
observable = RESOLVE(observable);
if (!observable) {
throw new Error(ERROR_OBSERVABLE_NO_SUPPORT);
}
if (typeof context === 'undefined') {
context = null;
}
args = [observable, type, handler, context];
MIDDLEWARE.run('listen', args);
observable = args[0];
type = args[1];
handler = args[2];
context = args[3];
args.splice(0, 4);
args = null;
current = LISTEN(observable, type, handler, context);
current.unlisten = createUnlistener(current);
current.head = last;
current.tail = null;
if (last) {
last.tail = current;
}
EVENTS = current;
return current.unlisten;
}
export
function un(observable, type, handler, context) {
var found, len, args;
if (!string(type)) {
throw new Error(ERROR_INVALID_TYPE);
}
if (!method(handler)) {
throw new Error(ERROR_INVALID_HANDLER);
}
observable = RESOLVE(observable);
if (!observable) {
throw new Error(ERROR_OBSERVABLE_NO_SUPPORT);
}
if (typeof context === 'undefined') {
context = null;
}
args = [observable, type, handler, context];
MIDDLEWARE.run('unlisten', args);
observable = args[0];
type = args[1];
handler = args[2];
context = args[3];
args.splice(0, 4);
args = null;
found = filter(4,
observable,
type,
handler,
context);
for (len = found.length; len--;) {
found[len].unlisten();
}
return getModule();
}
export
function dispatch(observable, type, properties) {
if (!string(type)) {
throw new Error(ERROR_INVALID_TYPE);
}
observable = RESOLVE(observable);
if (!observable) {
throw new Error(ERROR_OBSERVABLE_NO_SUPPORT);
}
if (arguments.length > 2 && !object(properties)) {
throw new Error(ERROR[1134]);
}
return DISPATCH(observable, type, properties || {});
}
export
function purge(observable, type, handler, context) {
var alen = arguments.length;
var found, len;
switch (true) {
case alen > 2:
if (!method(handler)) {
throw new Error(ERROR_INVALID_HANDLER);
}
/* falls through */
case alen > 1:
if (!string(type)) {
throw new Error(ERROR_INVALID_TYPE);
}
/* falls through */
case alen > 0:
if (!RESOLVE(observable)) {
throw new Error(ERROR_OBSERVABLE_NO_SUPPORT);
}
}
if (typeof context === 'undefined') {
context = null;
}
found = filter(alen, observable, type, handler, context);
len = found.length;
for (; len--;) {
found[len].unlisten();
}
return getModule();
}
export
function destructor(handler) {
if (!method(handler)) {
throw new Error(ERROR_INVALID_HANDLER);
}
MIDDLEWARE.register('global-destroy', handler);
return getModule();
}