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';
/*@
Ox.Element <f> Basic UI element object
# Arguments ----------------------------------------------------------------
(function(_) {
/*@
Ox.Element <f> Basic UI element object
# Arguments -----------------------------------------------------------
options <o|s> Options of the element, or just the `element` option
element <s> Tagname or CSS selector
tooltip <s|f> Tooltip title, or a function that returns one
(e) -> <s> Tooltip title
e <o> Mouse event
self <o> Shared private variable
# Usage --------------------------------------------------------------------
([options[, self]]) -> <o:Ox.JQueryElement> Element object
# Events ---------------------------------------------------------------
# Usage ---------------------------------------------------------------
([options[, self]]) -> Element object
# Events ----------------------------------------------------------
anyclick <!> anyclick
Fires on mouseup, but not on any subsequent mouseup within 250 ms
(this is useful if one wants to listen for singleclicks, but not
doubleclicks, since it will fire immediately, and won't fire again
in case of a doubleclick)
Fires on mouseup, but not on any subsequent mouseup within 250
ms (this is useful if one wants to listen for singleclicks, but
not doubleclicks, since it will fire immediately, and won't
fire again in case of a doubleclick)
* <*> Original event properties
doubleclick <!> doubleclick
Fires on the second mousedown within 250 ms (this is useful if one
wants to listen for both singleclicks and doubleclicks, since it
will not trigger a singleclick event)
Fires on the second mousedown within 250 ms (this is useful if
one wants to listen for both singleclicks and doubleclicks,
since it will not trigger a singleclick event)
* <*> Original event properties
drag <!> drag
Fires on mousemove after dragstart, stops firing on mouseup
@ -35,21 +37,22 @@ Ox.Element <f> Basic UI element object
* <*> Original event properties
dragenter <!> dragenter
Fires when entering an element during drag (this fires on the
element 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
clientDY <n> Vertical drag delta in px
* <*> Original event properties
dragleave <!> dragleave
Fires when leaving an element during drag (this fires on the element
being dragged -- the target element is the event's target property)
Fires when leaving an element during drag (this fires on the
element being dragged -- the target element is the event's
target property)
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> Original event properties
dragpause <!> dragpause
Fires once when the mouse doesn't move for 250 ms during drag (this
is useful in order to execute operations that are too expensive to
be attached to the drag event)
Fires once when the mouse doesn't move for 250 ms during drag
(this is useful in order to execute operations that are too
expensive to be attached to the drag event)
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> Original event properties
@ -58,14 +61,15 @@ Ox.Element <f> Basic UI element object
* <*> Original event properties
mousedown <!> mousedown
Fires on mousedown (this is useful if one wants to listen for
singleclicks, but not doubleclicks or drag events, and wants the
event to fire as early as possible)
singleclicks, but not doubleclicks or drag events, and wants
the event to fire as early as possible)
* <*> Original event properties
mouserepeat <!> mouserepeat
Fires every 50 ms after the mouse was down for 250 ms, stops firing
on mouseleave or mouseup (this fires like a key that is being
pressed and held, and is useful for buttons like scrollbars arrows
that need to react to both clicking and holding)
Fires every 50 ms after the mouse was down for 250 ms, stops
firing on mouseleave or mouseup (this fires like a key that is
being pressed and held, and is useful for buttons like
scrollbar arrows that need to react to both clicking and
holding)
mousewheel <!> mousewheel
Fires on mousewheel scroll or trackpad swipe
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
* <*> Original event properties
singleclick <!> singleclick
Fires 250 ms after mouseup, if there was no subsequent mousedown
(this is useful if one wants to listen for both singleclicks and
doubleclicks, since it will not fire for doubleclicks)
Fires 250 ms after mouseup, if there was no subsequent
mousedown (this is useful if one wants to listen for both
singleclicks and doubleclicks, since it will not fire for
doubleclicks)
* <*> Original event properties
*/
*/
Ox.Element = function(options, self) {
Ox.Element = function Element(options, self) {
// create private object
self = self || {};
// create defaults and options objects
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 || {};
// stack of callbacks bound to option updates
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
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')
.on(Ox.extend({
mousedown: mousedown,
mousewheel: mousewheel
}, self.options.element == '<iframe>' ? {
.data({oxid: that.oxid})
.on({
mousedown: onMousedown,
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() {
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();
@ -114,7 +144,7 @@ Ox.Element = function(options, self) {
}));
}
function mousedown(e) {
function onMousedown(e) {
/*
better mouse events
mousedown:
@ -225,24 +255,24 @@ Ox.Element = function(options, self) {
}
}
function mouseenter(e) {
function onMouseenter(e) {
if (!that.$tooltip) {
that.$tooltip = Ox.Tooltip({title: self.options.tooltip});
}
that.$tooltip.show(e);
}
function mouseleave(e) {
function onMouseleave(e) {
that.$tooltip && that.$tooltip.hide();
}
function mousemove(e) {
function onMousemove(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
e = e.originalEvent;
//e = e.originalEvent;
var absDelta,
deltaX = 'deltaX' in e ? e.deltaX
: 'wheelDeltaX' in e ? -e.wheelDeltaX
@ -280,8 +310,8 @@ Ox.Element = function(options, self) {
if (self.options.tooltip) {
if (Ox.isString(self.options.tooltip)) {
bindTooltipEvents({
mouseenter: mouseenter,
mouseleave: mouseleave
mouseenter: onMouseenter,
mouseleave: onMouseleave
});
that.$tooltip && that.$tooltip.options({
title: self.options.tooltip,
@ -290,8 +320,8 @@ Ox.Element = function(options, self) {
} else {
that.$tooltip = Ox.Tooltip({animate: false});
bindTooltipEvents({
mousemove: mousemove,
mouseleave: mouseleave
mousemove: onMousemove,
mouseleave: onMouseleave
});
}
} else {
@ -302,13 +332,26 @@ Ox.Element = function(options, self) {
}
}
function update(key, value) {
// update is called whenever an option is modified or added
Ox.loop(self.updateCallbacks.length - 1, -1, -1, function(i) {
// break if the callback returns false
return self.updateCallbacks[i](key, value) !== false;
that.update({tooltip: setTooltip});
return that;
};
// 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)
@ -323,9 +366,10 @@ Ox.Element = function(options, self) {
event <s> Event name
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)));
return that;
return this;
};
/*@
@ -341,9 +385,10 @@ Ox.Element = function(options, self) {
event <s> Event name
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)));
return that;
return this;
};
/*@
@ -358,7 +403,9 @@ Ox.Element = function(options, self) {
data <o> event data (key/value pairs)
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;
if (self.options.element == '<iframe>') {
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
() -> <o> object
@*/
that.bindKeyboard = function() {
Ox.Keyboard.bind(that.oxid);
return that;
Ox.Element.prototype.bindKeyboard = function bindKeyboard() {
Ox.Keyboard.bind(this.oxid);
return this;
};
that.childrenElements = function() {
return that.children().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
Ox.Element.prototype.childrenElements = function childrenElements() {
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
value <*> The value of the default option
@*/
that.defaults = function() {
Ox.Element.prototype.defaults = function defaults() {
var self = this.self(_);
var ret;
if (arguments.length == 0) {
ret = self.defaults;
@ -413,52 +461,53 @@ Ox.Element = function(options, self) {
} else {
self.defaults = arguments[0];
self.options = Ox.clone(self.defaults);
ret = that;
ret = this;
}
return ret;
};
that.findElements = function() {
return Ox.map(that.find('.OxElement'), Ox.UI.getOxElement);
Ox.Element.prototype.findElements = function findElements() {
return Ox.map(this.find('.OxElement'), Ox.UI.getOxElement);
};
that.forEach = function() {
Ox.$(that[0]).forEach.apply(null, arguments);
return that;
/*
Ox.Element.prototype.forEach = function forEach() {
// TODO
};
*/
/*@
gainFocus <function> Makes an element object gain focus
() -> <obj> This element object
@*/
that.gainFocus = function() {
Ox.Focus.focus(that.oxid);
return that;
Ox.Element.prototype.gainFocus = function gainFocus() {
Ox.Focus.focus(this.oxid);
return this;
};
/*@
hasFocus <function> Returns true if an element object has focus
() -> <boolean> True if the element has focus
@*/
that.hasFocus = function() {
return Ox.Focus.focused() == that.oxid;
Ox.Element.prototype.hasFocus = function hasFocus() {
return Ox.Focus.focused() == this.oxid;
};
/*@
loseFocus <function> Makes an element object lose focus
() -> <object> This element object
@*/
that.loseFocus = function() {
Ox.Focus.blur(that.oxid);
return that;
Ox.Element.prototype.loseFocus = function loseFocus() {
Ox.Focus.blur(this.oxid);
return this;
};
that.nextElement = function() {
return that.nextElements()[0];
Ox.Element.prototype.nextElement = function nextElement() {
return this.nextElements()[0];
};
that.nextElements = function() {
return that.nextAll().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
Ox.Element.prototype.nextElements = function nextElements() {
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
value <*> The value of the option
@*/
that.options = function() {
return Ox.getset(self.options, arguments, update, that);
Ox.Element.prototype.options = function options() {
var self = this.self(_);
return Ox.getset(self.options, arguments, self.update, this);
};
that.parentElement = function() {
return Ox.last(that.parentElements());
Ox.Element.prototype.parentElement = function parentElement() {
return Ox.last(this.parentElements());
};
that.parentElements = function() {
return that.parents().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
Ox.Element.prototype.parentElements = function parentElements() {
return this.parents().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
};
/*@
@ -492,40 +542,43 @@ Ox.Element = function(options, self) {
event <s> Event name
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>') {
Ox.Message.post(that, event, data);
Ox.Message.post(this, event, data);
}
return that;
return this;
};
that.prevElement = function() {
return Ox.last(that.prevElements());
Ox.Element.prototype.prevElement = function prevElement() {
return Ox.last(this.prevElements());
};
that.prevElements = function() {
return that.prevAll().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
Ox.Element.prototype.prevElements = function prevElements() {
return this.prevAll().filter(Ox.UI.isOxElement).map(Ox.UI.getOxElement);
};
/*@
remove <f> Removes an element object and its event handler
() -> <o> This element
@*/
that.remove = function(remove) {
remove !== false && that.find('.OxElement').each(function() {
var element = Ox.UI.elements[$(this).data('oxid')];
element && element.remove(false);
Ox.Element.prototype.remove = function remove(remove) {
if (remove !== false) {
this.find('.OxElement').each(function() {
var $element = Ox.elements[$(this).data('oxid')];
$element && $element.remove(false);
});
Ox.Focus.remove(that.oxid);
Ox.Keyboard.unbind(that.oxid);
delete Ox.UI.elements[that.oxid];
that.$tooltip && that.$tooltip.remove();
remove !== false && that.$element.remove();
return that;
}
Ox.Focus.remove(this.oxid);
Ox.Keyboard.unbind(this.oxid);
delete Ox.elements[this.oxid];
this.$tooltip && this.$tooltip.remove();
remove !== false && this.$element.remove();
return this;
};
/*
that.remove = function() {
Ox.Element.prototype.remove = function() {
[that].concat(that.find('.OxElement'))
.map(Ox.UI.getOxElement).forEach(function($element) {
$element.removeElement();
@ -534,7 +587,7 @@ Ox.Element = function(options, self) {
return that;
}
that.removeElement = function() {
Ox.Element.prototype.removeElement = function() {
delete Ox.$elements[that.oxid];
Ox.Focus.remove(that);
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
($element) -> <o> This element
@*/
that.setElement = function($element) {
$element.addClass('OxElement').data({oxid: that.oxid});
that.$element.replaceWith($element);
that.$element = $element;
that[0] = $element[0];
return that;
Ox.Element.prototype.setElement = function setElement($element) {
// FIXME: get rid of this.$element
$element.addClass('OxElement').data({oxid: this.oxid});
this.replaceWith($element);
this.$element = $element;
this[0] = $element[0];
this.elements = [this[0]];
return this;
};
/*@
toggleOption <f> Toggle boolean option(s)
(key[, key[, ...]]) -> <o> This element
@*/
that.toggleOption = function() {
Ox.Element.prototype.toggleOption = function toggleOption() {
var self = this.self(_);
var options = {};
Ox.slice(arguments).forEach(function(key) {
options[key] == !self.options[key];
});
that.options(options);
return that;
this.options(options);
return this;
};
/*@
@ -583,12 +639,13 @@ Ox.Element = function(options, self) {
event <string> Event name
data <object> Event data (key/value pairs)
@*/
that.triggerEvent = function() {
Ox.Event.trigger.apply(that, [self].concat(Ox.slice(arguments)));
return that;
Ox.Element.prototype.triggerEvent = function triggerEvent() {
var self = this.self(_);
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
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)));
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
() -> <o> object
@*/
that.unbindKeyboard = function() {
Ox.Keyboard.unbind(that.oxid);
return that;
Ox.Element.prototype.unbindKeyboard = function unbindKeyboard() {
Ox.Keyboard.unbind(this.oxid);
return this;
};
/*@
@ -631,7 +689,8 @@ Ox.Element = function(options, self) {
(key, callback) -> <o> that
({key: callback, ...}) -> <o> that
@*/
that.update = function() {
Ox.Element.prototype.update = function update() {
var self = this.self(_);
var callbacks;
if (Ox.isFunction(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
@*/
that.value = function() {
return that.options(
Ox.Element.prototype.value = function value() {
return this.options(
arguments.length == 0 ? 'value' : {value: arguments[0]}
);
};
that.update({tooltip: setTooltip});
}({}));
return that;
};