oxjs/source/Ox.UI/js/Core/Element.js

532 lines
19 KiB
JavaScript
Raw Normal View History

2011-11-05 16:46:53 +00:00
'use strict';
2011-05-05 18:02:56 +00:00
/*@
2012-06-02 12:10:47 +00:00
Ox.Element <f> Basic UI element object
2011-05-05 18:02:56 +00:00
# Arguments ----------------------------------------------------------------
2012-06-02 12:10:47 +00:00
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 ---------------------------------------------------------------
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)
* <*> 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)
* <*> Original event properties
drag <!> drag
Fires on mousemove after dragstart, stops firing on mouseup
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> Original event properties
dragend <!> dragpause
Fires on mouseup after dragstart
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> 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)
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)
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)
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> 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)
* <*> Original event properties
dragstart <!> dragstart
Fires when the mouse is down for 250 ms
* <*> 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)
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)
* <*> Original event properties
*/
2011-04-22 22:03:10 +00:00
Ox.Element = function(options, self) {
2011-04-22 22:03:10 +00:00
// create private object
self = self || {};
// create defaults and options objects
self.defaults = {};
// allow for Ox.TestElement('<tagname>') or Ox.TestElement('cssSelector')
self.options = Ox.isString(options) ? {element: options} : options || {};
// the actual event handler
self.$eventHandler = self.$eventHandler || $('<div>');
2012-05-28 16:16:23 +00:00
// array of callbacks bound to any event
self.eventCallbacks = self.eventCallbacks || [];
2012-06-26 13:12:09 +00:00
// stack of callbacks bound to option updates
self.updateCallbacks = self.updateCallbacks || [];
2011-04-22 22:03:10 +00:00
// create public object
var that = new Ox.JQueryElement($(self.options.element || '<div>'))
.addClass('OxElement')
2012-06-26 14:33:26 +00:00
.on({mousedown: mousedown});
setTooltip();
2011-04-29 22:07:23 +00:00
function bind(event, callback, once) {
self.$eventHandler[
once ? 'one' : 'on'
]('ox_' + event, function(event, data) {
call(callback, data, event);
});
}
function call(callback, data, event) {
2012-06-25 18:40:06 +00:00
event.ox_id = that.oxid;
event.ox_type = event.type.replace(/^ox_/, '');
callback.call(that, data || {}, event);
}
function mousedown(e) {
/*
better mouse events
mousedown:
trigger mousedown
within 250 msec:
mouseup: trigger anyclick
mouseup + mousedown: trigger doubleclick
after 250 msec:
mouseup + no mousedown within 250 msec: trigger singleclick
no mouseup within 250 msec:
trigger mouserepeat every 50 msec
trigger dragstart
mousemove: trigger drag
no mousemove for 250 msec:
trigger dragpause
mouseup: trigger dragend
"anyclick" is not called "click" since this would collide with the click
events of some widgets
*/
var clientX, clientY,
dragTimeout = 0,
mouseInterval = 0;
if (!self._mouseTimeout) {
// first mousedown
that.triggerEvent('mousedown', e);
self._drag = false;
self._mouseup = false;
self._mouseTimeout = setTimeout(function() {
// 250 ms later, no subsequent click
self._mouseTimeout = 0;
if (self._mouseup) {
// mouse went up, trigger singleclick
that.triggerEvent('singleclick', e);
} else {
// mouse is still down, trigger mouserepeat
// every 50 ms until mouseleave or mouseup
mouserepeat();
mouseInterval = setInterval(mouserepeat, 50);
that.one('mouseleave', function() {
clearInterval(mouseInterval);
});
// trigger dragstart, set up drag events
that.triggerEvent('dragstart', e);
2012-06-16 16:10:19 +00:00
$('.OxElement').on({
mouseenter: dragenter,
mouseleave: dragleave
});
clientX = e.clientX;
clientY = e.clientY;
Ox.UI.$window
.off('mouseup', mouseup)
.mousemove(mousemove)
.one('mouseup', function(e) {
// stop checking for mouserepeat
2011-04-22 22:03:10 +00:00
clearInterval(mouseInterval);
// stop checking for dragpause
clearTimeout(dragTimeout);
// stop checking for drag
2012-06-26 14:33:26 +00:00
Ox.UI.$window.off({mousemove: mousemove});
// stop checking for dragenter and dragleave
2012-06-16 16:10:19 +00:00
$('.OxElement').off({
mouseenter: dragenter,
mouseleave: dragleave
});
// trigger dragend
that.triggerEvent('dragend', extend(e));
2011-04-22 22:03:10 +00:00
});
self._drag = true;
2011-04-22 22:03:10 +00:00
}
}, 250);
} else {
// second mousedown within 250 ms, trigger doubleclick
clearTimeout(self._mouseTimeout);
self._mouseTimeout = 0;
that.triggerEvent('doubleclick', e);
2011-04-22 22:03:10 +00:00
}
Ox.UI.$window.one({mouseup: mouseup});
function dragenter(e) {
that.triggerEvent('dragenter', extend(e));
2011-04-22 22:03:10 +00:00
}
function dragleave(e) {
that.triggerEvent('dragleave', extend(e));
}
function extend(e) {
return Ox.extend({
clientDX: e.clientX - clientX,
clientDY: e.clientY - clientY
}, e);
2011-04-22 22:03:10 +00:00
}
function mousemove(e) {
e = extend(e);
2011-11-03 15:42:41 +00:00
clearTimeout(dragTimeout);
dragTimeout = setTimeout(function() {
// mouse did not move for 250 ms, trigger dragpause
that.triggerEvent('dragpause', e);
}, 250);
that.triggerEvent('drag', e);
}
function mouserepeat(e) {
that.triggerEvent('mouserepeat', e);
2011-04-22 22:03:10 +00:00
}
function mouseup(e) {
if (!self._mouseup && !self._drag) {
// mouse went up for the first time, trigger anyclick
that.triggerEvent('anyclick', e);
self._mouseup = true;
}
}
}
2011-04-22 22:03:10 +00:00
function mouseenter(e) {
that.$tooltip.show(e);
}
2011-04-22 22:03:10 +00:00
function mouseleave(e) {
that.$tooltip.hide();
}
2011-04-22 22:03:10 +00:00
function mousemove(e) {
that.$tooltip.options({title: self.options.tooltip(e)}).show(e);
}
2011-04-22 22:03:10 +00:00
// TODO: in other widgets, use this,
// rather than some self.$tooltip that
// will not get garbage collected
function setTooltip() {
if (self.options.tooltip) {
if (Ox.isString(self.options.tooltip)) {
that.$tooltip = Ox.Tooltip({title: self.options.tooltip});
that.on({mouseenter: mouseenter}).off({mousemove: mousemove});
} else {
that.$tooltip = Ox.Tooltip({animate: false});
that.on({mousemove: mousemove}).off({mouseenter: mouseenter});
}
that.on({mouseleave: mouseleave});
} else {
if (that.$tooltip) {
that.$tooltip.remove();
that.off({
mouseenter: mouseenter,
mousemove: mousemove,
mouseleave: mouseleave
});
}
}
}
2012-05-28 16:16:23 +00:00
function update(key, value) {
// update is called whenever an option is modified or added
Ox.loop(self.updateCallbacks.length - 1, -1, -1, function(i) {
self.updateCallbacks[i](key, value) === false && Ox.Break();
});
}
2011-04-22 22:03:10 +00:00
/*@
bindEvent <function> Binds a function to an event
(callback) -> <o> This element
(event, callback) -> <o> This element
({event: callback, ...}) -> <o> This element
callback <f> Callback function
data <o> event data (key/value pairs)
event <s> Event name
Event names can be namespaced, like `'click.foo'`
@*/
that.bindEvent = function() {
if (Ox.typeOf(arguments[0]) == 'function') {
self.eventCallbacks.push(arguments[0]);
} else {
Ox.forEach(Ox.makeObject(arguments), function(callback, event) {
bind(event, callback);
});
}
return that;
};
2011-04-22 22:03:10 +00:00
/*@
bindEventOnce <function> Binds a function to an event, once
(event, callback) -> <obj> This element object
({event: callback, ...}) -> <obj> This element object
callback <f> Callback function
data <o> event data (key/value pairs)
event <s> Event name
Event names can be namespaced, like `'click.foo'`
@*/
that.bindEventOnce = function() {
Ox.forEach(Ox.makeObject(arguments), function(callback, event) {
bind(event, callback, true);
});
return that;
};
2011-04-22 22:03:10 +00:00
2012-05-21 10:38:18 +00:00
/*@
bindKeyboard <f> bind keyboard
() -> <o> object
@*/
that.bindKeyboard = function() {
2012-05-22 14:08:09 +00:00
Ox.Keyboard.bind(that.oxid);
return that;
};
/*@
2011-12-21 13:42:47 +00:00
defaults <function> Gets or sets the default options for an element object
2012-06-22 23:54:33 +00:00
({key: value, ...}) -> <obj> This element object
key <str> The name of the default option
value <*> The value of the default option
@*/
2011-12-21 13:42:47 +00:00
that.defaults = function() {
var ret;
if (arguments.length == 0) {
ret = self.defaults;
} else if (Ox.isString(arguments[0])) {
ret = self.defaults[arguments[0]];
} else {
self.defaults = arguments[0];
self.options = Ox.clone(self.defaults);
ret = that;
}
return ret;
};
2011-05-05 18:02:56 +00:00
/*@
gainFocus <function> Makes an element object gain focus
() -> <obj> This element object
@*/
that.gainFocus = function() {
2012-05-22 14:08:09 +00:00
Ox.Focus.focus(that.oxid);
return that;
};
2011-04-22 22:03:10 +00:00
/*@
hasFocus <function> Returns true if an element object has focus
() -> <boolean> True if the element has focus
@*/
that.hasFocus = function() {
2012-05-22 14:08:09 +00:00
return Ox.Focus.focused() == that.oxid;
};
2011-05-05 18:02:56 +00:00
/*@
loseFocus <function> Makes an element object lose focus
() -> <object> This element object
@*/
that.loseFocus = function() {
2012-05-22 14:08:09 +00:00
Ox.Focus.blur(that.oxid);
return that;
};
2011-04-22 22:03:10 +00:00
/*@
2012-06-02 10:37:19 +00:00
options <f> Gets or sets the options of an element object
# Usage
2012-06-02 10:37:19 +00:00
() -> <o> All options
(key) -> <*> The value of option[key]
(key, value) -> <o> This element
2012-05-28 19:35:41 +00:00
Sets options[key] to value and calls update(key, value)
if the key/value pair was added or modified
2012-06-02 10:37:19 +00:00
({key: value, ...}) -> <o> this element
2012-05-28 19:35:41 +00:00
Sets multiple options and calls update(key, value)
for every key/value pair that was added or modified
# Arguments
2012-06-02 10:37:19 +00:00
key <s> The name of the option
value <*> The value of the option
@*/
that.options = function() {
2012-05-28 16:16:23 +00:00
return Ox.getset(self.options, arguments, update, that);
};
/*@
2012-06-02 10:37:19 +00:00
removeElement <f> Removes an element object and its event handler
() -> <o> This element
@*/
that.remove = function(remove) {
2012-05-22 13:14:40 +00:00
remove !== false && that.find('.OxElement').each(function() {
2011-11-01 23:14:29 +00:00
var oxid = $(this).data('oxid'),
element = Ox.UI.elements[oxid];
element && element.remove(false);
});
2012-05-22 14:08:09 +00:00
Ox.Focus.remove(that.oxid);
Ox.Keyboard.unbind(that.oxid);
delete self.$eventHandler;
2012-05-22 14:08:09 +00:00
delete Ox.UI.elements[that.oxid];
2011-11-01 23:14:29 +00:00
that.$tooltip && that.$tooltip.remove();
remove !== false && that.$element.remove();
return that;
};
2012-05-21 10:38:18 +00:00
/*@
setElement <f> set $element
2012-06-02 10:00:52 +00:00
($element) -> <o> This element
2012-05-21 10:38:18 +00:00
@*/
2011-12-21 13:42:47 +00:00
that.setElement = function($element) {
2012-05-22 14:08:09 +00:00
$element.addClass('OxElement').data({oxid: that.oxid});
2011-12-21 13:42:47 +00:00
that.$element.replaceWith($element);
that.$element = $element;
2012-06-26 16:01:13 +00:00
that[0] = $element[0];
2012-06-02 10:00:52 +00:00
return that;
2011-12-21 13:42:47 +00:00
};
2012-05-21 10:38:18 +00:00
/*@
2012-06-02 10:00:52 +00:00
toggleOption <f> Toggle boolean option(s)
(key[, key[, ...]]) -> <o> This element
2012-05-21 10:38:18 +00:00
@*/
2011-12-21 13:42:47 +00:00
that.toggleOption = function() {
var options = {};
2012-06-02 10:00:52 +00:00
Ox.toArray(arguments).forEach(function(key) {
2011-12-21 13:42:47 +00:00
options[key] == !self.options[key];
});
that.options(options);
2012-06-02 10:00:52 +00:00
return that;
2011-12-21 13:42:47 +00:00
};
/*@
triggerEvent <function> Triggers an event
(event) -> <object> This element object
(event, data) -> <object> This element object
({event: data, ...}) -> <object> This element object
event <string> Event name
data <object> Event data (key/value pairs)
@*/
that.triggerEvent = function() {
Ox.forEach(Ox.makeObject(arguments), function(data, event) {
var type = 'ox_' + event;
// FIXME: remove this
if ([
'mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick',
'dragstart', 'drag', 'dragenter', 'dragleave', 'dragpause', 'dragend',
'draganddropstart', 'draganddrop', 'draganddropenter', 'draganddropleave', 'draganddropend',
2012-02-04 11:44:19 +00:00
'playing', 'position', 'progress', 'request'
].indexOf(event) == -1) {
if (!/^pandora_/.test(event)) {
2012-05-22 14:08:09 +00:00
Ox.Log('EVENT', that.oxid, self.options.id, 'trigger', event, data);
}
2011-04-22 22:03:10 +00:00
}
// it is necessary to check if self.$eventHandler exists,
// since, for example, when removing the element on click,
// singleclick will fire after the removal of the event handler
self.$eventHandler && self.$eventHandler.trigger(type, data);
self.eventCallbacks.forEach(function(callback) {
call(callback, data, {type: type});
});
});
return that;
};
2011-04-22 22:03:10 +00:00
/*@
unbindEvent <function> Unbinds all callbacks from an event
To unbind a specific handler, use namespaced events, like
`bindEvent('click.foo', callback)`, and then `unbindEvent('click.foo')`.
() -> <object> This element
Unbinds all callbacks from all events
(callback) -> <o> This element
Unbinds one callback from all events
(event) -> <o> This element
Unbinds all callbacks from one event
(event, callback) -> <o> This element
Unbinds one callback from one event
({event: callback}, ...) -> <o> This element
Unbinds multiple callbacks from multiple events
event <string> Event name
@*/
that.unbindEvent = function() {
var callback = arguments[0];
if (arguments.length == 0) {
self.eventCallbacks = [];
self.$eventHandler.off();
} else if (Ox.typeOf(callback) == 'function') {
self.eventCallbacks = self.eventCallbacks.filter(function(fn) {
return fn !== callback;
});
} else {
2012-06-04 12:07:35 +00:00
Ox.forEach(Ox.makeObject(arguments), function(callback, event) {
self.$eventHandler.off('ox_' + event, callback);
});
}
2011-04-22 22:03:10 +00:00
return that;
};
2012-05-21 10:38:18 +00:00
/*@
unbindKeyboard <f> unbind keyboard
() -> <o> object
@*/
that.unbindKeyboard = function() {
2012-05-22 14:08:09 +00:00
Ox.Keyboard.unbind(that.oxid);
return that;
};
2012-05-28 16:16:23 +00:00
/*@
update <f> Adds one or more handlers for options updates
(callback) -> <o> that
(key, callback) -> <o> that
({key: callback, ...}) -> <o> that
@*/
that.update = function() {
var callbacks;
2012-06-25 14:32:16 +00:00
if (Ox.isFunction(arguments[0])) {
2012-05-28 16:16:23 +00:00
self.updateCallbacks.push(arguments[0]);
} else {
callbacks = Ox.makeObject(arguments);
self.updateCallbacks.push(function(key) {
if (callbacks[key]) {
return callbacks[key]();
}
});
}
return that;
};
2011-12-21 15:33:52 +00:00
/*@
value <f> Shortcut to get or set self.options.value
@*/
that.value = function() {
return that.options(
arguments.length == 0 ? 'value' : {value: arguments[0]}
);
};
that.update({tooltip: setTooltip});
2012-05-28 16:16:23 +00:00
return that;
2011-04-22 22:03:10 +00:00
};