mirror of
https://github.com/embarklabs/neo-blessed.git
synced 2025-01-10 19:16:20 +00:00
add event bubbling.
This commit is contained in:
parent
0e047b8e51
commit
b2666ffecb
171
lib/events.js
Normal file
171
lib/events.js
Normal file
@ -0,0 +1,171 @@
|
||||
/**
|
||||
* events.js - event emitter for blessed
|
||||
* Copyright (c) 2013, Christopher Jeffrey (MIT License)
|
||||
* https://github.com/chjj/blessed
|
||||
*/
|
||||
|
||||
var slice = Array.prototype.slice;
|
||||
|
||||
/**
|
||||
* EventEmitter
|
||||
*/
|
||||
|
||||
function EventEmitter() {
|
||||
if (!this._events) this._events = {};
|
||||
}
|
||||
|
||||
EventEmitter.prototype.addListener = function(type, listener) {
|
||||
if (!this._events[type]) {
|
||||
this._events[type] = listener;
|
||||
} else if (typeof this._events[type] === 'function') {
|
||||
this._events[type] = [this._events[type], listener];
|
||||
} else {
|
||||
this._events[type].push(listener);
|
||||
}
|
||||
this._emit('newListener', [type, listener]);
|
||||
};
|
||||
|
||||
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
|
||||
|
||||
EventEmitter.prototype.removeListener = function(type, listener) {
|
||||
var handler = this._events[type];
|
||||
if (!handler) return;
|
||||
|
||||
if (typeof handler === 'function' || handler.length === 1) {
|
||||
delete this._events[type];
|
||||
this._emit('removeListener', [type, listener]);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < handler.length; i++) {
|
||||
if (handler[i] === listener || handler[i].listener === listener) {
|
||||
handler.splice(i, 1);
|
||||
this._emit('removeListener', [type, listener]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
|
||||
|
||||
EventEmitter.prototype.removeAllListeners = function(type) {
|
||||
if (type) {
|
||||
delete this._events[type];
|
||||
} else {
|
||||
this._events = {};
|
||||
}
|
||||
};
|
||||
|
||||
EventEmitter.prototype.once = function(type, listener) {
|
||||
function on() {
|
||||
this.removeListener(type, on);
|
||||
return listener.apply(this, arguments);
|
||||
}
|
||||
on.listener = listener;
|
||||
return this.on(type, on);
|
||||
};
|
||||
|
||||
EventEmitter.prototype.listeners = function(type) {
|
||||
return typeof this._events[type] === 'function'
|
||||
? [this._events[type]]
|
||||
: this._events[type] || [];
|
||||
};
|
||||
|
||||
EventEmitter.prototype._emit = function(type, args) {
|
||||
var handler = this._events[type]
|
||||
, ret;
|
||||
|
||||
if (!handler) {
|
||||
if (type === 'error') {
|
||||
throw new Error('Unhandled error event: '
|
||||
+ JSON.stringify(slice.call(arguments)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof handler === 'function') {
|
||||
return handler.apply(this, args);
|
||||
}
|
||||
|
||||
for (var i = 0; i < handler.length; i++) {
|
||||
if (handler[i].apply(this, args) === false) {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
return ret !== false;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.emit = function(type) {
|
||||
var args = slice.call(arguments, 1)
|
||||
, el = this;
|
||||
|
||||
if (this.type === 'screen') {
|
||||
return this._emit(type, args);
|
||||
}
|
||||
|
||||
this._emit(type, args);
|
||||
|
||||
type = 'element ' + type;
|
||||
args.unshift(this);
|
||||
|
||||
do {
|
||||
if (!el._events[type]) continue;
|
||||
if (el._emit(type, args) === false) {
|
||||
return false;
|
||||
}
|
||||
} while (el = el.parent);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// For hooking into the main EventEmitter if we want to.
|
||||
// Might be better to do things this way being that it
|
||||
// will always be compatible with node, not to mention
|
||||
// it gives us domain support as well.
|
||||
// Node.prototype._emit = Node.prototype.emit;
|
||||
// Node.prototype.emit = function(type) {
|
||||
// var args, el;
|
||||
//
|
||||
// if (this.type === 'screen') {
|
||||
// return this._emit.apply(this, arguments);
|
||||
// }
|
||||
//
|
||||
// this._emit.apply(this, arguments);
|
||||
//
|
||||
// args = slice.call(arguments, 1);
|
||||
// el = this;
|
||||
//
|
||||
// args.unshift('element ' + type, this);
|
||||
// this._bubbleStopped = false;
|
||||
// //args.push(stopBubble);
|
||||
//
|
||||
// do {
|
||||
// if (!el._events || !el._events[type]) continue;
|
||||
// el._emit.apply(el, args);
|
||||
// if (this._bubbleStopped) return false;
|
||||
// } while (el = el.parent);
|
||||
//
|
||||
// return true;
|
||||
// };
|
||||
//
|
||||
// Node.prototype._addListener = Node.prototype.addListener;
|
||||
// Node.prototype.on =
|
||||
// Node.prototype.addListener = function(type, listener) {
|
||||
// function on() {
|
||||
// if (listener.apply(this, arguments) === false) {
|
||||
// this._bubbleStopped = true;
|
||||
// }
|
||||
// }
|
||||
// on.listener = listener;
|
||||
// return this._addListener(type, on);
|
||||
// };
|
||||
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
||||
exports = EventEmitter;
|
||||
exports.EventEmitter = EventEmitter;
|
||||
|
||||
module.exports = exports;
|
@ -9,7 +9,7 @@
|
||||
* Modules
|
||||
*/
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
var EventEmitter = require('./events').EventEmitter
|
||||
, assert = require('assert')
|
||||
, path = require('path')
|
||||
, fs = require('fs')
|
||||
@ -325,7 +325,6 @@ function Screen(options) {
|
||||
el.emit('resize');
|
||||
el.children.forEach(emit);
|
||||
})(self);
|
||||
// self.emitDescendants('resize');
|
||||
}
|
||||
|
||||
this.program.on('resize', function() {
|
||||
@ -427,6 +426,10 @@ Screen.prototype._listenMouse = function(el) {
|
||||
|
||||
if (el && !~this.clickable.indexOf(el)) {
|
||||
el.clickable = true;
|
||||
if (el.options.autoFocus && !el._autoFocused) {
|
||||
el._autoFocused = true;
|
||||
el.on('element click', el.focus.bind(el));
|
||||
}
|
||||
this.clickable.push(el);
|
||||
}
|
||||
|
||||
@ -435,8 +438,6 @@ Screen.prototype._listenMouse = function(el) {
|
||||
|
||||
this.program.enableMouse();
|
||||
|
||||
// this.on('element click', el.focus.bind(el));
|
||||
|
||||
this.program.on('mouse', function(data) {
|
||||
if (self.lockKeys) return;
|
||||
|
||||
@ -472,10 +473,8 @@ Screen.prototype._listenMouse = function(el) {
|
||||
if (data.x >= left && data.x < left + width
|
||||
&& data.y >= top && data.y < top + height) {
|
||||
el.emit('mouse', data);
|
||||
self.emit('element mouse', el, data);
|
||||
if (data.action === 'mouseup') {
|
||||
el.emit('click', data);
|
||||
self.emit('element click', el, data);
|
||||
} else if (data.action === 'mousemove') {
|
||||
if (self.hover && el.index > self.hover.index) {
|
||||
set = false;
|
||||
@ -483,28 +482,14 @@ Screen.prototype._listenMouse = function(el) {
|
||||
if (self.hover !== el && !set) {
|
||||
if (self.hover) {
|
||||
self.hover.emit('mouseout', data);
|
||||
self.emit('element mouseout', self.hover, data);
|
||||
}
|
||||
el.emit('mouseover', data);
|
||||
self.emit('element mouseover', el, data);
|
||||
self.hover = el;
|
||||
}
|
||||
set = true;
|
||||
}
|
||||
el.emit(data.action, data);
|
||||
self.emit('element ' + data.action, el, data);
|
||||
|
||||
// XXX Temporary workaround for List wheel events.
|
||||
// Make sure they get propogated to the list instead of the list item.
|
||||
if ((data.action === 'wheeldown' || data.action === 'wheelup')
|
||||
&& el.listeners(data.action).length === 0
|
||||
&& el.parent.listeners(data.action).length > 0) {
|
||||
el.parent.emit(data.action, data);
|
||||
self.emit('element ' + data.action, el.parent, data);
|
||||
}
|
||||
|
||||
// Seems to work better without this if statement:
|
||||
// if (data.action !== 'mousemove')
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -516,7 +501,6 @@ Screen.prototype._listenMouse = function(el) {
|
||||
&& self.hover
|
||||
&& !set) {
|
||||
self.hover.emit('mouseout', data);
|
||||
self.emit('element mouseout', self.hover, data);
|
||||
self.hover = null;
|
||||
}
|
||||
|
||||
@ -532,10 +516,12 @@ Screen.prototype._listenKeys = function(el) {
|
||||
el.keyable = true;
|
||||
// Listen for click, but do not enable
|
||||
// mouse if it's not enabled yet.
|
||||
if (el.options.autoFocus !== false) {
|
||||
if (el.options.autoFocus !== false && !el._autoFocused) {
|
||||
el._autoFocused = true;
|
||||
var lm = this._listenedMouse;
|
||||
this._listenedMouse = true;
|
||||
el.on('click', el.focus.bind(el));
|
||||
this._listenMouse(el);
|
||||
el.on('element click', el.focus.bind(el));
|
||||
this._listenedMouse = lm;
|
||||
}
|
||||
this.keyable.push(el);
|
||||
@ -570,8 +556,6 @@ Screen.prototype._listenKeys = function(el) {
|
||||
if (~self.keyable.indexOf(focused)) {
|
||||
focused.emit('keypress', ch, key);
|
||||
focused.emit('key ' + key.full, ch, key);
|
||||
self.emit('element keypress', focused, ch, key);
|
||||
self.emit('element key ' + key.full, focused, ch, key);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -1438,7 +1422,6 @@ Screen.prototype.rewindFocus = function() {
|
||||
|
||||
if (old) {
|
||||
old.emit('blur');
|
||||
this.screen.emit('element blur', old);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1470,11 +1453,9 @@ Screen.prototype._focus = function(self, old) {
|
||||
|
||||
if (old) {
|
||||
old.emit('blur', self);
|
||||
self.screen.emit('element blur', old, self);
|
||||
}
|
||||
|
||||
self.emit('focus', old);
|
||||
self.screen.emit('element focus', old, self);
|
||||
};
|
||||
|
||||
Screen.prototype.__defineGetter__('focused', function() {
|
||||
@ -1838,6 +1819,7 @@ function Element(options) {
|
||||
|
||||
// TODO: Possibly move this to Node for screen.on('mouse', ...).
|
||||
this.on('newListener', function fn(type) {
|
||||
//type = type.split(' ').slice(1).join(' ');
|
||||
if (type === 'mouse'
|
||||
|| type === 'click'
|
||||
|| type === 'mouseover'
|
||||
@ -3503,11 +3485,11 @@ function ScrollableBox(options) {
|
||||
}
|
||||
|
||||
if (options.mouse) {
|
||||
this.on('wheeldown', function(data) {
|
||||
this.on('wheeldown', function(el, data) {
|
||||
self.scroll(self.height / 2 | 0 || 1);
|
||||
self.screen.render();
|
||||
});
|
||||
this.on('wheelup', function(data) {
|
||||
this.on('wheelup', function(el, data) {
|
||||
self.scroll(-(self.height / 2 | 0) || -1);
|
||||
self.screen.render();
|
||||
});
|
||||
@ -3808,12 +3790,16 @@ function List(options) {
|
||||
}
|
||||
|
||||
if (options.mouse) {
|
||||
this.on('wheeldown', function(data) {
|
||||
this.screen._listenMouse(this);
|
||||
//this.screen.on('element wheeldown', function(el, data) {
|
||||
this.on('element wheeldown', function(el, data) {
|
||||
// if (el !== self && !el.hasAncestor(self)) return;
|
||||
self.select(self.selected + 2);
|
||||
self.screen.render();
|
||||
});
|
||||
|
||||
this.on('wheelup', function(data) {
|
||||
//this.screen.on('element wheelup', function(el, data) {
|
||||
this.on('element wheelup', function(el, data) {
|
||||
// if (el !== self && !el.hasAncestor(self)) return;
|
||||
self.select(self.selected - 2);
|
||||
self.screen.render();
|
||||
});
|
||||
@ -4063,16 +4049,15 @@ function Form(options) {
|
||||
|
||||
if (options.keys) {
|
||||
this.screen._listenKeys(this);
|
||||
this.screen.on('element keypress', function(el, ch, key) {
|
||||
//this.screen.on('element keypress', function(el, ch, key) {
|
||||
this.on('element keypress', function(el, ch, key) {
|
||||
// Make sure we're not entering input into a textbox.
|
||||
// if (self.screen.grabKeys || self.screen.lockKeys) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Make sure we're a form or input element.
|
||||
if (el !== self && !el.hasAncestor(self)) {
|
||||
return;
|
||||
}
|
||||
// if (el !== self && !el.hasAncestor(self)) return;
|
||||
|
||||
if ((key.name === 'tab' && !key.shift)
|
||||
|| key.name === 'down'
|
||||
|
@ -183,7 +183,7 @@ stext.on('keypress', function(ch, key) {
|
||||
}
|
||||
});
|
||||
|
||||
screen.on('element focus', function(old, cur) {
|
||||
screen.on('element focus', function(cur, old) {
|
||||
if (old.border) old.style.border.fg = 'default';
|
||||
if (cur.border) cur.style.border.fg = 'green';
|
||||
screen.render();
|
||||
|
Loading…
x
Reference in New Issue
Block a user