unified Event/Keyboard/Message handler

This commit is contained in:
rlx 2014-09-23 21:12:31 +02:00
parent 4cf10359ef
commit cc183b6198

View file

@ -1,197 +1,310 @@
'use strict';
(function() {
/*@
Ox.Event <o> Event controller
@*/
var chars = {
comma: ',',
dot: '.',
minus: '-',
quote: '\'',
semicolon: ';',
slash: '/',
space: ' '
},
hasCallback = {},
keyboardEventRegExp = /^key(\.[\w\d.]+)?$/,
keys = '',
keysEventRegExp = new RegExp(
'^[\\w\\d](\\.numpad)?$|^(' + Object.keys(chars).join('|') + ')$'
),
resetTimeout,
triggerTimeout;
Ox.Event = (function() {
function bind(options) {
var args = Ox.slice(arguments, 1),
callbacks = options.callbacks,
that = this;
Ox.forEach(
Ox.isFunction(args[0]) ? {'*': args[0]} : Ox.makeObject(args),
function(originalCallback, event) {
event = event.replace(/^key_/, 'key.');
callbacks[event] = (callbacks[event] || []).concat(
options.once ? function callback() {
unbind.call(
that, {callbacks: callbacks}, event, callback
);
return originalCallback.apply(null, arguments);
}
: originalCallback
);
if (isKeyboardEvent(event)) {
hasCallback[event] = true;
}
}
);
return this;
}
var eventHandlers = [],
that = {};
function isKeyboardEvent(event) {
return keyboardEventRegExp.test(event);
}
function log(data, event, self) {
var element = this,
handlers = self.eventHandlers ? self.eventHandlers[event] : [];
if (!Ox.contains([
'mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick', 'mousewheel',
'dragstart', 'drag', 'dragenter', 'dragleave', 'dragpause', 'dragend',
'draganddropstart', 'draganddrop', 'draganddropenter', 'draganddropleave', 'draganddropend',
'playing', 'position', 'progress'
], event)) {
try {
data = JSON.stringify(data)
} catch(e) {}
Ox.print(
'EVENT',
element.oxid,
'"' + element[0].className.split(' ').filter(function(className) {
return /^Ox/.test(className);
}).map(function(className) {
return className.replace(/^Ox/, '');
}).join(' ') + '"',
event,
data,
handlers.length,
handlers.map(function(handler) {
return handler.toString().split('\n').shift();
})
);
function isKeysEventKey(key) {
return keysEventRegExp.test(key);
}
function onMessage(e) {
var element, message = {};
try {
message = Ox.extend({data: {}}, JSON.parse(e.data));
} catch (e) {}
if (message.event == 'init') {
if (message.data.oxid) {
// The inner window receives the oxid of the outer iframe element
Ox.oxid = message.data.oxid;
Ox.parent.postMessage('init', {})
} else if (message.target) {
// The outer window receives init from iframe
Ox.elements[message.target].triggerEvent('init');
}
} else {
(message.target ? Ox.elements[message.target] : Ox.parent)
.triggerMessage(message.event, message.data);
}
}
/*@
.bind <f> Adds event handler(s)
(callback) -> <o> Ox.Event
Adds a global event handler
(self, callback) -> <o> Ox.Event
Adds a catch-all handler
(self, event, callback) -> <o> Ox.Event
Adds a handler for a single event
(self, {event: callback, ...}) -> <o> Ox.Event
Adds handlers for multiple events
self <o> The element's shared private object
callback <f> Callback function
data <o> Event data
event <s> Event name
element <o> Element
event <s> Event name
Event names can be namespaced, like `'click.foo'`
*/
that.bind = function() {
var args = Ox.slice(arguments), once, self;
if (args.length == 1) {
eventHandlers.push(args[0])
} else {
self = args.shift();
once = Ox.isBoolean(Ox.last(args)) ? args.pop() : false;
args = Ox.isFunction(args[0]) ? {'*': args[0]} : Ox.makeObject(args);
if (Ox.len(args) && !self.eventHandlers) {
self.eventHandlers = {};
}
Ox.forEach(args, function(callback, event) {
self.eventHandlers[event] = (
self.eventHandlers[event] || []
).concat({callback: callback, once: once});
});
}
return that;
};
/*@
.bindOnce <f> Adds event handler(s) that run(s) only once
(self, callback) -> <o> Ox.Event
Adds a catch-all handler
(self, event, callback) -> <o> Ox.Event
Adds an event handler for a single event
(self, {event: callback, ...}) -> <o> Ox.Event
Adds event handlers for multiple events
self <o> The element's shared private object
callback <f> Callback function
data <o> Event data
event <s> Event name
element <o> Element
event <s> Event name
Event names can be namespaced, like `'click.foo'`
*/
that.bindOnce = function() {
return that.bind.apply(null, Ox.slice(arguments).concat(true));
};
/*@
.log <f> Turns event logging on or off
(enabled) -> <o> Ox.Event
enabled <b> Enables (`true`) or disables (`false`) event logging
*/
that.log = function(enabled) {
that[enabled ? 'bind' : 'unbind'](log);
return that;
};
/*@
.trigger <f> Triggers an event
(self, event) -> <o> Ox.Event
Triggers an event
(self, event, data) -> <o> Ox.Event
Triggers an event with data
(self, {event: data, ...}) -> Ox.Event
Triggers multiple events with data
self <o> The element's shared private object
event <s> Event name
data <o> Event data
*/
that.trigger = function(self) {
var args = arguments, element = this;
if (self.eventHandlers) {
Ox.forEach(Ox.makeObject(Ox.slice(args, 1)), function(data, event) {
var triggered = event.split('.');
eventHandlers.forEach(function(callback) {
callback.call(element, data || {}, event, element);
});
triggered.map(function(v, i) {
return triggered.slice(0, i + 1).join('.');
}).concat('*').forEach(function(triggered) {
var handlers = self.eventHandlers[triggered];
handlers && handlers.forEach(function(handler, i) {
handler.once && handlers.splice(i, 1);
handler.callback.call(element, data || {}, event);
});
});
});
}
return that;
};
/*@
.unbind <f> Removes an event handler
() -> Ox.Event
Removes all global handlers
(callback) -> <o> Ox.Event
Removes a global handler
(self) -> <o> Ox.Event
Removes all handlers
(self, callback) -> <o> Ox.Event
Removes a specific catch-all handler
(self, event) -> <o> Ox.Event
Remove all handlers for a single event
(self, event, callback) -> <o> Ox.Event
Removes a specific handler for a single event
(self, {event: callback, ...}) -> <o> Ox.Event
Removes specific event handlers for multiple events
self <o> The element's shared private object
callback <f> Callback function
event <s> Event name
*/
that.unbind = function() {
var args = Ox.slice(arguments), self;
if (args.length == 0) {
eventHandlers = [];
} else if (Ox.isFunction(args[0])) {
eventHandlers.forEach(function(handler, i) {
handler === args[0] && eventHandlers.splice(i, 1);
});
} else if ((self = args.shift()).eventHandlers) {
if (args.length == 0) {
delete self.eventHandlers;
} else {
if (Ox.isFunction(args[0])) {
args = {'*': args[0]};
function onKeydown(e) {
var $element = Ox.Focus.focusedElement(),
isInput = Ox.Focus.focusedElementIsInput(),
keyName = Ox.KEYS[e.keyCode],
keyBasename = keyName.split('.')[0],
key = Object.keys(Ox.MODIFIER_KEYS).filter(function(key) {
return e[key] && Ox.MODIFIER_KEYS[key] != keyBasename;
}).map(function(key) {
return Ox.MODIFIER_KEYS[key];
}).concat(keyName).join('_'),
triggerEvent = function() {
if ($element) {
$element.triggerEvent.apply($element, arguments);
} else {
Ox.Event.trigger.apply(
Ox.$body, [{}].concat(Ox.slice(arguments))
);
}
Ox.forEach(Ox.makeObject(args), function(callback, event) {
if (Ox.isUndefined(callback)) {
delete self.eventHandlers[event];
} else {
self.eventHandlers[event].forEach(function(handler, i) {
if (handler.callback === callback) {
self.eventHandlers[event].splice(i, 1);
}
});
}
});
};
triggerEvent('key.' + key, e);
if (isKeysEventKey(key)) {
// don't register leading spaces or trailing double spaces
if (keyName != 'space' || (
keys != '' && !Ox.endsWith(keys, ' ')
)) {
keys += chars[keyName] || keyBasename;
// clear the trigger timeout only if the key registered
clearTimeout(triggerTimeout);
triggerTimeout = setTimeout(function() {
triggerEvent('keys', Ox.extend(e, {keys: keys}));
}, 250);
}
}
// clear the reset timeout even if the key didn't register
clearTimeout(resetTimeout);
resetTimeout = setTimeout(function() {
keys = '';
}, 1000);
if (hasCallback[key]) {
e.preventDefault();
}
}
function trigger(options) {
var args = Ox.slice(arguments, 1),
callbacks = options.callbacks,
that = this;
Ox.forEach(Ox.makeObject(args), function(data, originalEvent) {
var events = originalEvent.split('.');
['*'].concat(events.map(function(event, index) {
return events.slice(0, index + 1).join('.');
})).forEach(function(event) {
(callbacks[0][event] || [])
.concat(callbacks[1][event] || [])
.forEach(function(callback) {
callback.call(that, data, originalEvent, that);
});
});
});
return this;
}
function unbind(options) {
var args = Ox.slice(arguments, 1),
callbacks = options.callbacks;
if (args.length == 0) {
// unbind all handlers for all events
callbacks = [];
} else {
Ox.forEach(
Ox.isFunction(args[0]) ? {'*': args[0]}
: Ox.makeObject(args),
function(callback, event) {
if (!callback) {
// unbind all handlers for this event
delete callbacks[event];
} else if (callbacks[event]) {
// unbind this handler for this event
callbacks[event] = callbacks[event].filter(
function(eventCallback) {
return eventCallback !== callback;
}
);
if (callbacks[event].length == 0) {
delete callbacks[event];
}
}
if (isKeyboardEvent(event) && !callbacks[event]) {
delete hasCallback[event];
}
}
);
}
return this;
}
Ox.$parent = Ox.parent = (function() {
var self = {messageCallbacks: {}},
that = {oxid: Ox.uid()};
that.bindMessage = function() {
return Ox.Message.bind.apply(
this, [self].concat(Ox.slice(arguments))
);
};
that.bindMessageOnce = function() {
return Ox.Message.bindOnce.apply(
this, [self].concat(Ox.slice(arguments))
);
};
that.postMessage = function() {
return Ox.Message.post.apply(this, Ox.slice(arguments));
};
that.triggerMessage = function() {
return Ox.Message.trigger.apply(
this, [self].concat(Ox.slice(arguments))
);
};
that.unbindMessage = function() {
return Ox.Message.unbind.apply(
this, [self].concat(Ox.slice(arguments))
);
};
return that;
};
return that;
}());
}());
Ox.Event = (function() {
var callbacks = {},
that = {};
that.bind = function() {
var isElement = this !== that;
return bind.apply(this, [{
callbacks: isElement ? arguments[0].eventCallbacks : callbacks
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
that.bindOnce = function() {
var isElement = this !== that;
return bind.apply(this, [{
callbacks: isElement ? arguments[0].eventCallbacks : callbacks,
once: true
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
that.trigger = function(self) {
return trigger.apply(this, [{
callbacks: [callbacks, self.eventCallbacks || {}]
}].concat(Ox.slice(arguments, 1)));
};
that.unbind = function() {
var isElement = this !== that;
return unbind.apply(this, [{
callbacks: isElement ? arguments[0].eventCallbacks : callbacks
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
return that;
}());
Ox.Message = (function() {
var callbacks = {},
that = {};
that.bind = function() {
var isElement = this !== that;
return bind.apply(this, [{
callbacks: isElement ? arguments[0].messageCallbacks
: callbacks
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
that.bindOnce = function() {
var isElement = this !== that;
return bind.apply(this, [{
callbacks: isElement ? arguments[0].messageCallbacks
: callbacks,
once: true
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
that.post = function() {
var args = arguments,
isParent = this == Ox.parent,
target = isParent ? window.parent : this[0].contentWindow,
that = this;
if (isParent && !Ox.oxid) {
// posting to parent, but not yet initialized
setTimeout(function() {
that.post.apply(Ox.parent, args);
}, 25);
}
Ox.forEach(
Ox.makeObject(Ox.slice(args)),
function(data, event) {
target.postMessage(JSON.stringify({
data: data,
event: event,
target: isParent ? Ox.oxid : null
}), '*');
}
);
};
that.trigger = function(self) {
return trigger.apply(this, [{
callbacks: [callbacks, self.messageCallbacks]
}].concat(Ox.slice(arguments, 1)));
};
that.unbind = function() {
var isElement = this !== that;
return unbind.apply(this, [{
callbacks: isElement ? arguments[0].messageCallbacks
: callbacks
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
return that;
}());
document.addEventListener('keydown', onKeydown);
window.addEventListener('message', onMessage);
}());