prototypal Ox.Element

This commit is contained in:
rlx 2014-09-22 16:56:54 +02:00
parent e4cb30724f
commit 972ccbbfb8

View file

@ -1,27 +1,29 @@
'use strict'; 'use strict';
(function(_) {
/*@ /*@
Ox.Element <f> Basic UI element object Ox.Element <f> Basic UI element object
# Arguments ---------------------------------------------------------------- # Arguments -----------------------------------------------------------
options <o|s> Options of the element, or just the `element` option options <o|s> Options of the element, or just the `element` option
element <s> Tagname or CSS selector element <s> Tagname or CSS selector
tooltip <s|f> Tooltip title, or a function that returns one tooltip <s|f> Tooltip title, or a function that returns one
(e) -> <s> Tooltip title (e) -> <s> Tooltip title
e <o> Mouse event e <o> Mouse event
self <o> Shared private variable self <o> Shared private variable
# Usage -------------------------------------------------------------------- # Usage ---------------------------------------------------------------
([options[, self]]) -> <o:Ox.JQueryElement> Element object ([options[, self]]) -> Element object
# Events --------------------------------------------------------------- # Events ----------------------------------------------------------
anyclick <!> anyclick anyclick <!> anyclick
Fires on mouseup, but not on any subsequent mouseup within 250 ms Fires on mouseup, but not on any subsequent mouseup within 250
(this is useful if one wants to listen for singleclicks, but not ms (this is useful if one wants to listen for singleclicks, but
doubleclicks, since it will fire immediately, and won't fire again not doubleclicks, since it will fire immediately, and won't
in case of a doubleclick) fire again in case of a doubleclick)
* <*> Original event properties * <*> Original event properties
doubleclick <!> doubleclick doubleclick <!> doubleclick
Fires on the second mousedown within 250 ms (this is useful if one Fires on the second mousedown within 250 ms (this is useful if
wants to listen for both singleclicks and doubleclicks, since it one wants to listen for both singleclicks and doubleclicks,
will not trigger a singleclick event) since it will not trigger a singleclick event)
* <*> Original event properties * <*> Original event properties
drag <!> drag drag <!> drag
Fires on mousemove after dragstart, stops firing on mouseup Fires on mousemove after dragstart, stops firing on mouseup
@ -35,21 +37,22 @@ Ox.Element <f> Basic UI element object
* <*> Original event properties * <*> Original event properties
dragenter <!> dragenter dragenter <!> dragenter
Fires when entering an element during drag (this fires on the Fires when entering an element during drag (this fires on the
element being dragged -- the target element is the event's target element being dragged -- the target element is the event's
property) target property)
clientDX <n> Horizontal drag delta in px clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px clientDY <n> Vertical drag delta in px
* <*> Original event properties * <*> Original event properties
dragleave <!> dragleave dragleave <!> dragleave
Fires when leaving an element during drag (this fires on the element Fires when leaving an element during drag (this fires on the
being dragged -- the target element is the event's target property) element being dragged -- the target element is the event's
target property)
clientDX <n> Horizontal drag delta in px clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px clientDY <n> Vertical drag delta in px
* <*> Original event properties * <*> Original event properties
dragpause <!> dragpause dragpause <!> dragpause
Fires once when the mouse doesn't move for 250 ms during drag (this Fires once when the mouse doesn't move for 250 ms during drag
is useful in order to execute operations that are too expensive to (this is useful in order to execute operations that are too
be attached to the drag event) expensive to be attached to the drag event)
clientDX <n> Horizontal drag delta in px clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px clientDY <n> Vertical drag delta in px
* <*> Original event properties * <*> Original event properties
@ -58,14 +61,15 @@ Ox.Element <f> Basic UI element object
* <*> Original event properties * <*> Original event properties
mousedown <!> mousedown mousedown <!> mousedown
Fires on mousedown (this is useful if one wants to listen for Fires on mousedown (this is useful if one wants to listen for
singleclicks, but not doubleclicks or drag events, and wants the singleclicks, but not doubleclicks or drag events, and wants
event to fire as early as possible) the event to fire as early as possible)
* <*> Original event properties * <*> Original event properties
mouserepeat <!> mouserepeat mouserepeat <!> mouserepeat
Fires every 50 ms after the mouse was down for 250 ms, stops firing Fires every 50 ms after the mouse was down for 250 ms, stops
on mouseleave or mouseup (this fires like a key that is being firing on mouseleave or mouseup (this fires like a key that is
pressed and held, and is useful for buttons like scrollbars arrows being pressed and held, and is useful for buttons like
that need to react to both clicking and holding) scrollbar arrows that need to react to both clicking and
holding)
mousewheel <!> mousewheel mousewheel <!> mousewheel
Fires on mousewheel scroll or trackpad swipe Fires on mousewheel scroll or trackpad swipe
deltaFactor <n> Original delta = normalized delta * delta factor deltaFactor <n> Original delta = normalized delta * delta factor
@ -73,36 +77,62 @@ Ox.Element <f> Basic UI element object
deltaY <n> Normalized vertical scroll delta in px deltaY <n> Normalized vertical scroll delta in px
* <*> Original event properties * <*> Original event properties
singleclick <!> singleclick singleclick <!> singleclick
Fires 250 ms after mouseup, if there was no subsequent mousedown Fires 250 ms after mouseup, if there was no subsequent
(this is useful if one wants to listen for both singleclicks and mousedown (this is useful if one wants to listen for both
doubleclicks, since it will not fire for doubleclicks) singleclicks and doubleclicks, since it will not fire for
doubleclicks)
* <*> Original event properties * <*> Original event properties
*/ */
Ox.Element = function(options, self) { Ox.Element = function Element(options, self) {
// create private object // create private object
self = self || {}; self = self || {};
// create defaults and options objects // create defaults and options objects
self.defaults = {}; self.defaults = {};
// allow for Ox.TestElement('<tagname>') or Ox.TestElement('cssSelector') // allow for Ox.Element('<tagname>') or Ox.Element('cssSelector')
self.options = Ox.isString(options) ? {element: options} : options || {}; self.options = Ox.isString(options) ? {element: options} : options || {};
// stack of callbacks bound to option updates // stack of callbacks bound to option updates
self.updateCallbacks = self.updateCallbacks || []; self.updateCallbacks = self.updateCallbacks || [];
self.boundTooltipEvents = {}; self.boundTooltipEvents = {}; // FIXME?
self.data = {};
self.update = function update(key, value) {
// update is called whenever an option is modified or added
Ox.loop(self.updateCallbacks.length - 1, -1, -1, function(index) {
// break if the callback returns false
return self.updateCallbacks[index](key, value) !== false;
});
};
// create public object // create public object
var that = new Ox.JQueryElement($(self.options.element || '<div>')) var that = Object.create(Ox.Element.prototype);
that.oxid = Ox.uid();
that.$element = $(self.options.element || '<div>')
.addClass('OxElement') .addClass('OxElement')
.on(Ox.extend({ .data({oxid: that.oxid})
mousedown: mousedown, .on({
mousewheel: mousewheel mousedown: onMousedown,
}, self.options.element == '<iframe>' ? { mousewheel: onMousewheel
});
that[0] = that.$element[0];
that.length = 1;
Ox.elements[that.oxid] = that;
if (self.options.element == '<iframe>') {
// FIXME: update later
that.on({
load: function() { load: function() {
Ox.Message.post(that, 'init', {id: that.oxid}); Ox.Message.post(that, 'init', {id: that.oxid});
} }
} : {})); });
}
that.self = function() {
// this function has to be anonymous in order not to shadow self
return arguments[0] === _ ? self : {};
};
setTooltip(); setTooltip();
@ -114,7 +144,7 @@ Ox.Element = function(options, self) {
})); }));
} }
function mousedown(e) { function onMousedown(e) {
/* /*
better mouse events better mouse events
mousedown: mousedown:
@ -225,24 +255,24 @@ Ox.Element = function(options, self) {
} }
} }
function mouseenter(e) { function onMouseenter(e) {
if (!that.$tooltip) { if (!that.$tooltip) {
that.$tooltip = Ox.Tooltip({title: self.options.tooltip}); that.$tooltip = Ox.Tooltip({title: self.options.tooltip});
} }
that.$tooltip.show(e); that.$tooltip.show(e);
} }
function mouseleave(e) { function onMouseleave(e) {
that.$tooltip && that.$tooltip.hide(); that.$tooltip && that.$tooltip.hide();
} }
function mousemove(e) { function onMousemove(e) {
that.$tooltip.options({title: self.options.tooltip(e)}).show(e); that.$tooltip.options({title: self.options.tooltip(e)}).show(e);
} }
function mousewheel(e) { function onMousewheel(e) {
// see https://github.com/brandonaaron/jquery-mousewheel/blob/master/jquery.mousewheel.js // see https://github.com/brandonaaron/jquery-mousewheel/blob/master/jquery.mousewheel.js
e = e.originalEvent; //e = e.originalEvent;
var absDelta, var absDelta,
deltaX = 'deltaX' in e ? e.deltaX deltaX = 'deltaX' in e ? e.deltaX
: 'wheelDeltaX' in e ? -e.wheelDeltaX : 'wheelDeltaX' in e ? -e.wheelDeltaX
@ -280,8 +310,8 @@ Ox.Element = function(options, self) {
if (self.options.tooltip) { if (self.options.tooltip) {
if (Ox.isString(self.options.tooltip)) { if (Ox.isString(self.options.tooltip)) {
bindTooltipEvents({ bindTooltipEvents({
mouseenter: mouseenter, mouseenter: onMouseenter,
mouseleave: mouseleave mouseleave: onMouseleave
}); });
that.$tooltip && that.$tooltip.options({ that.$tooltip && that.$tooltip.options({
title: self.options.tooltip, title: self.options.tooltip,
@ -290,8 +320,8 @@ Ox.Element = function(options, self) {
} else { } else {
that.$tooltip = Ox.Tooltip({animate: false}); that.$tooltip = Ox.Tooltip({animate: false});
bindTooltipEvents({ bindTooltipEvents({
mousemove: mousemove, mousemove: onMousemove,
mouseleave: mouseleave mouseleave: onMouseleave
}); });
} }
} else { } else {
@ -302,13 +332,26 @@ Ox.Element = function(options, self) {
} }
} }
function update(key, value) { that.update({tooltip: setTooltip});
// update is called whenever an option is modified or added
Ox.loop(self.updateCallbacks.length - 1, -1, -1, function(i) { return that;
// break if the callback returns false
return self.updateCallbacks[i](key, value) !== false; };
// add all jQuery methods to the prototype of Ox.Element
Ox.methods($('<div>'), true).forEach(function(method) {
Ox.Element.prototype[method] = function() {
var $element = this.$element[method].apply(
this.$element, arguments
),
oxid;
// if exactly one $element of an Ox object was returned, then return the
// Ox object instead, so that we can do oxObj.jqFn().oxFn()
return $element && $element.jquery && $element.length == 1
&& Ox.elements[oxid = $element.data('oxid')]
? Ox.elements[oxid] : $element;
};
}); });
}
/*@ /*@
bindEvent <f> Adds event handler(s) bindEvent <f> Adds event handler(s)
@ -323,9 +366,10 @@ Ox.Element = function(options, self) {
event <s> Event name event <s> Event name
Event names can be namespaced, like `'click.foo'` Event names can be namespaced, like `'click.foo'`
@*/ @*/
that.bindEvent = function() { Ox.Element.prototype.bindEvent = function bindEvent() {
var self = this.self(_);
Ox.Event.bind.apply(null, [self].concat(Ox.slice(arguments))); Ox.Event.bind.apply(null, [self].concat(Ox.slice(arguments)));
return that; return this;
}; };
/*@ /*@
@ -341,9 +385,10 @@ Ox.Element = function(options, self) {
event <s> Event name event <s> Event name
Event names can be namespaced, like `'click.foo'` Event names can be namespaced, like `'click.foo'`
@*/ @*/
that.bindEventOnce = function() { Ox.Element.prototype.bindEventOnce = function bindEventOnce() {
var self = this.self(_);
Ox.Event.bindOnce.apply(null, [self].concat(Ox.slice(arguments))); Ox.Event.bindOnce.apply(null, [self].concat(Ox.slice(arguments)));
return that; return this;
}; };
/*@ /*@
@ -358,7 +403,9 @@ Ox.Element = function(options, self) {
data <o> event data (key/value pairs) data <o> event data (key/value pairs)
event <s> Event name event <s> Event name
@*/ @*/
that.bindMessage = that.onMessage = function() { Ox.Element.prototype.bindMessage = Ox.Element.prototype.onMessage = function bindMessage() {
var self = this.self(_);
var that = this;
var callback; var callback;
if (self.options.element == '<iframe>') { if (self.options.element == '<iframe>') {
if (Ox.isObject(arguments[0])) { if (Ox.isObject(arguments[0])) {
@ -378,10 +425,10 @@ Ox.Element = function(options, self) {
}); });
} }
} }
return that; return this;
}; };
that.bindMessageOnce = function() { Ox.Element.prototype.bindMessageOnce = function bindMessageOnce() {
}; };
@ -389,13 +436,13 @@ Ox.Element = function(options, self) {
bindKeyboard <f> bind keyboard bindKeyboard <f> bind keyboard
() -> <o> object () -> <o> object
@*/ @*/
that.bindKeyboard = function() { Ox.Element.prototype.bindKeyboard = function bindKeyboard() {
Ox.Keyboard.bind(that.oxid); Ox.Keyboard.bind(this.oxid);
return that; return this;
}; };
that.childrenElements = function() { Ox.Element.prototype.childrenElements = function childrenElements() {
return that.children().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement); return this.children().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
}; };
/*@ /*@
@ -404,7 +451,8 @@ Ox.Element = function(options, self) {
key <str> The name of the default option key <str> The name of the default option
value <*> The value of the default option value <*> The value of the default option
@*/ @*/
that.defaults = function() { Ox.Element.prototype.defaults = function defaults() {
var self = this.self(_);
var ret; var ret;
if (arguments.length == 0) { if (arguments.length == 0) {
ret = self.defaults; ret = self.defaults;
@ -413,52 +461,53 @@ Ox.Element = function(options, self) {
} else { } else {
self.defaults = arguments[0]; self.defaults = arguments[0];
self.options = Ox.clone(self.defaults); self.options = Ox.clone(self.defaults);
ret = that; ret = this;
} }
return ret; return ret;
}; };
that.findElements = function() { Ox.Element.prototype.findElements = function findElements() {
return Ox.map(that.find('.OxElement'), Ox.UI.getOxElement); return Ox.map(this.find('.OxElement'), Ox.UI.getOxElement);
}; };
that.forEach = function() { /*
Ox.$(that[0]).forEach.apply(null, arguments); Ox.Element.prototype.forEach = function forEach() {
return that; // TODO
}; };
*/
/*@ /*@
gainFocus <function> Makes an element object gain focus gainFocus <function> Makes an element object gain focus
() -> <obj> This element object () -> <obj> This element object
@*/ @*/
that.gainFocus = function() { Ox.Element.prototype.gainFocus = function gainFocus() {
Ox.Focus.focus(that.oxid); Ox.Focus.focus(this.oxid);
return that; return this;
}; };
/*@ /*@
hasFocus <function> Returns true if an element object has focus hasFocus <function> Returns true if an element object has focus
() -> <boolean> True if the element has focus () -> <boolean> True if the element has focus
@*/ @*/
that.hasFocus = function() { Ox.Element.prototype.hasFocus = function hasFocus() {
return Ox.Focus.focused() == that.oxid; return Ox.Focus.focused() == this.oxid;
}; };
/*@ /*@
loseFocus <function> Makes an element object lose focus loseFocus <function> Makes an element object lose focus
() -> <object> This element object () -> <object> This element object
@*/ @*/
that.loseFocus = function() { Ox.Element.prototype.loseFocus = function loseFocus() {
Ox.Focus.blur(that.oxid); Ox.Focus.blur(this.oxid);
return that; return this;
}; };
that.nextElement = function() { Ox.Element.prototype.nextElement = function nextElement() {
return that.nextElements()[0]; return this.nextElements()[0];
}; };
that.nextElements = function() { Ox.Element.prototype.nextElements = function nextElements() {
return that.nextAll().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement); return this.nextAll().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
}; };
/*@ /*@
@ -474,16 +523,17 @@ Ox.Element = function(options, self) {
key <s> The name of the option key <s> The name of the option
value <*> The value of the option value <*> The value of the option
@*/ @*/
that.options = function() { Ox.Element.prototype.options = function options() {
return Ox.getset(self.options, arguments, update, that); var self = this.self(_);
return Ox.getset(self.options, arguments, self.update, this);
}; };
that.parentElement = function() { Ox.Element.prototype.parentElement = function parentElement() {
return Ox.last(that.parentElements()); return Ox.last(this.parentElements());
}; };
that.parentElements = function() { Ox.Element.prototype.parentElements = function parentElements() {
return that.parents().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement); return this.parents().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
}; };
/*@ /*@
@ -492,40 +542,43 @@ Ox.Element = function(options, self) {
event <s> Event name event <s> Event name
data <o> Event data data <o> Event data
@*/ @*/
that.postMessage = function(event, data) { Ox.Element.prototype.postMessage = function postMessage(event, data) {
var self = this.self(_);
if (self.options.element == '<iframe>') { if (self.options.element == '<iframe>') {
Ox.Message.post(that, event, data); Ox.Message.post(this, event, data);
} }
return that; return this;
}; };
that.prevElement = function() { Ox.Element.prototype.prevElement = function prevElement() {
return Ox.last(that.prevElements()); return Ox.last(this.prevElements());
}; };
that.prevElements = function() { Ox.Element.prototype.prevElements = function prevElements() {
return that.prevAll().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement); return this.prevAll().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
}; };
/*@ /*@
remove <f> Removes an element object and its event handler remove <f> Removes an element object and its event handler
() -> <o> This element () -> <o> This element
@*/ @*/
that.remove = function(remove) { Ox.Element.prototype.remove = function remove(remove) {
remove !== false && that.find('.OxElement').each(function() { if (remove !== false) {
var element = Ox.UI.elements[$(this).data('oxid')]; this.find('.OxElement').each(function() {
element && element.remove(false); var $element = Ox.elements[$(this).data('oxid')];
$element && $element.remove(false);
}); });
Ox.Focus.remove(that.oxid); }
Ox.Keyboard.unbind(that.oxid); Ox.Focus.remove(this.oxid);
delete Ox.UI.elements[that.oxid]; Ox.Keyboard.unbind(this.oxid);
that.$tooltip && that.$tooltip.remove(); delete Ox.elements[this.oxid];
remove !== false && that.$element.remove(); this.$tooltip && this.$tooltip.remove();
return that; remove !== false && this.$element.remove();
return this;
}; };
/* /*
that.remove = function() { Ox.Element.prototype.remove = function() {
[that].concat(that.find('.OxElement')) [that].concat(that.find('.OxElement'))
.map(Ox.UI.getOxElement).forEach(function($element) { .map(Ox.UI.getOxElement).forEach(function($element) {
$element.removeElement(); $element.removeElement();
@ -534,7 +587,7 @@ Ox.Element = function(options, self) {
return that; return that;
} }
that.removeElement = function() { Ox.Element.prototype.removeElement = function() {
delete Ox.$elements[that.oxid]; delete Ox.$elements[that.oxid];
Ox.Focus.remove(that); Ox.Focus.remove(that);
self.unbindKeyboard(); self.unbindKeyboard();
@ -542,34 +595,37 @@ Ox.Element = function(options, self) {
}; };
*/ */
that.removeElement = function() { Ox.Element.prototype.removeElement = function removeElement() {
// ... // ...
return that; return this;
}; };
/*@ /*@
setElement <f> set $element setElement <f> set $element
($element) -> <o> This element ($element) -> <o> This element
@*/ @*/
that.setElement = function($element) { Ox.Element.prototype.setElement = function setElement($element) {
$element.addClass('OxElement').data({oxid: that.oxid}); // FIXME: get rid of this.$element
that.$element.replaceWith($element); $element.addClass('OxElement').data({oxid: this.oxid});
that.$element = $element; this.replaceWith($element);
that[0] = $element[0]; this.$element = $element;
return that; this[0] = $element[0];
this.elements = [this[0]];
return this;
}; };
/*@ /*@
toggleOption <f> Toggle boolean option(s) toggleOption <f> Toggle boolean option(s)
(key[, key[, ...]]) -> <o> This element (key[, key[, ...]]) -> <o> This element
@*/ @*/
that.toggleOption = function() { Ox.Element.prototype.toggleOption = function toggleOption() {
var self = this.self(_);
var options = {}; var options = {};
Ox.slice(arguments).forEach(function(key) { Ox.slice(arguments).forEach(function(key) {
options[key] == !self.options[key]; options[key] == !self.options[key];
}); });
that.options(options); this.options(options);
return that; return this;
}; };
/*@ /*@
@ -583,12 +639,13 @@ Ox.Element = function(options, self) {
event <string> Event name event <string> Event name
data <object> Event data (key/value pairs) data <object> Event data (key/value pairs)
@*/ @*/
that.triggerEvent = function() { Ox.Element.prototype.triggerEvent = function triggerEvent() {
Ox.Event.trigger.apply(that, [self].concat(Ox.slice(arguments))); var self = this.self(_);
return that; Ox.Event.trigger.apply(this, [self].concat(Ox.slice(arguments)));
return this;
}; };
that.triggerMessage = function() { Ox.Element.prototype.triggerMessage = function triggerMessage() {
}; };
@ -607,12 +664,13 @@ Ox.Element = function(options, self) {
Removes specific handlers for one or more events Removes specific handlers for one or more events
event <string> Event name event <string> Event name
@*/ @*/
that.unbindEvent = function() { Ox.Element.prototype.unbindEvent = function unbindEvent() {
var self = this.self(_);
Ox.Event.unbind.apply(null, [self].concat(Ox.slice(arguments))); Ox.Event.unbind.apply(null, [self].concat(Ox.slice(arguments)));
return that; return this;
}; };
that.unbindMessage = function() { Ox.Element.prototype.unbindMessage = function unbindMessage() {
}; };
@ -620,9 +678,9 @@ Ox.Element = function(options, self) {
unbindKeyboard <f> unbind keyboard unbindKeyboard <f> unbind keyboard
() -> <o> object () -> <o> object
@*/ @*/
that.unbindKeyboard = function() { Ox.Element.prototype.unbindKeyboard = function unbindKeyboard() {
Ox.Keyboard.unbind(that.oxid); Ox.Keyboard.unbind(this.oxid);
return that; return this;
}; };
/*@ /*@
@ -631,7 +689,8 @@ Ox.Element = function(options, self) {
(key, callback) -> <o> that (key, callback) -> <o> that
({key: callback, ...}) -> <o> that ({key: callback, ...}) -> <o> that
@*/ @*/
that.update = function() { Ox.Element.prototype.update = function update() {
var self = this.self(_);
var callbacks; var callbacks;
if (Ox.isFunction(arguments[0])) { if (Ox.isFunction(arguments[0])) {
self.updateCallbacks.push(arguments[0]); self.updateCallbacks.push(arguments[0]);
@ -643,20 +702,17 @@ Ox.Element = function(options, self) {
} }
}); });
} }
return that; return this;
}; };
/*@ /*@
value <f> Shortcut to get or set self.options.value value <f> Shortcut to get or set self.options.value
@*/ @*/
that.value = function() { Ox.Element.prototype.value = function value() {
return that.options( return this.options(
arguments.length == 0 ? 'value' : {value: arguments[0]} arguments.length == 0 ? 'value' : {value: arguments[0]}
); );
}; };
that.update({tooltip: setTooltip}); }({}));
return that;
};