// vim: et:ts=4:sw=4:sts=4:ft=javascript // check out http://ejohn.org/apps/learn/#36 (-#38, making fns work w/o new) /*@ Ox.Element Basic UI element object # Usage -------------------------------------------------------------------- ([options[, self]]) -> UI element # Arguments ---------------------------------------------------------------- options the options of the element # Properties element tagname or CSS selector tooltip tooltip title or function that returns one (e) -> tooltip title e mouse event options tagname or CSS selector self shared private variable # Events ------------------------------------------------------------------- anyclick anyclick fires on mouseup, but not on any subsequent mouseup within 250 ms * <*> original event properties doubleclick doubleclick fires on the second mousedown within 250 ms * <*> original event properties drag drag fires on mousemove after dragstart, stops firing on mouseup clientDX horizontal drag delta in px clientDY vertical drag delta in px * <*> original event properties dragend dragpause Fires on mouseup after dragstart clientDX horizontal drag delta in px clientDY vertical drag delta in px * <*> original event properties dragpause dragpause Fires once when the mouse doesn't move for 250 ms after drag clientDX horizontal drag delta in px clientDY vertical drag delta in px * <*> 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 * <*> original event properties singleclick singleclick fires 250 ms after mouseup, if there was no subsequent mousedown * <*> original event properties @*/ Ox.Element = function(options, self) { /* // allow for 'Ox.Element()' instead of 'Ox.Element()' if (!(this instanceof arguments.callee)) { return new arguments.callee(options, self); } */ // create private object self = self || {}; // create defaults and options objects self.defaults = {}; self.options = options || {}; // allow for Ox.TestElement('') // or Ox.TestElement('cssSelector') if (Ox.isString(self.options)) { self.options = { element: self.options }; } // create event handler if (!self.$eventHandler) { self.$eventHandler = $('
'); } // create public object var that = new Ox.JQueryElement( $(self.options.element || '
') ) .mousedown(mousedown); if (self.options.tooltip) { if (Ox.isString(self.options.tooltip)) { that.$tooltip = Ox.Tooltip({ title: self.options.tooltip, }); that.bind({ mouseenter: mouseenter }); } else { that.$tooltip = Ox.Tooltip({ animate: false }); that.bind({ mousemove: mousemove }); } that.bind({ mouseleave: mouseleave }); } function bind(action, event, fn) { self.$eventHandler[action]('ox_' + event, function(event, data) { // fixme: remove second parameter fn(Ox.extend({ _element: that.$element, _event: event }, data), data); }); } function mousedown(e) { /* better mouse events mousedown: trigger mousedown within 250 msec: mouseup: trigger anyclick ("click" would collide with click events of certain widgets) 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 within 250 msec: trigger dragpause mouseup: trigger dragend */ var clientX, clientY, dragTimeout = 0, mouseInterval = 0; if (!self.mouseTimeout) { // first mousedown that.triggerEvent('mousedown', e); self.mouseup = false; self.mouseTimeout = setTimeout(function() { self.mouseTimeout = 0; if (self.mouseup) { // singleclick that.triggerEvent('singleclick', e); } else { // mouserepeat, drag clientX = e.clientX; clientY = e.clientY; that.triggerEvent('dragstart', e); mouserepeat(); mouseInterval = setInterval(mouserepeat, 50); Ox.UI.$window.unbind('mouseup', mouseup) .mousemove(mousemove) .one('mouseup', function(e) { clearInterval(mouseInterval); clearTimeout(dragTimeout); Ox.UI.$window.unbind('mousemove', mousemove); that.triggerEvent('dragend', extend(e)); }); that.one('mouseleave', function() { clearInterval(mouseInterval); }); } }, 250); } else { // second mousedown clearTimeout(self.mouseTimeout); self.mouseTimeout = 0; that.triggerEvent('doubleclick', e); } Ox.UI.$window.one('mouseup', mouseup); function extend(e) { return Ox.extend({ clientDX: e.clientX - clientX, clientDY: e.clientY - clientY }, e); } function mousemove(e) { e = extend(e); clearTimeout(dragTimeout); dragTimeout = setTimeout(function() { that.triggerEvent('dragpause', e); }, 250); that.triggerEvent('drag', e); } function mouserepeat() { that.triggerEvent('mouserepeat'); } function mouseup(e) { // only trigger on first mouseup if (!self.mouseup) { that.triggerEvent('anyclick', e); self.mouseup = true; } } } function mouseenter(e) { that.$tooltip.show(e); } function mouseleave(e) { that.$tooltip.hide(); } function mousemove(e) { //Ox.print('mousemove!!') that.$tooltip.options({ title: self.options.tooltip(e) }).show(e); } self.setOption = function() { // self.setOptions(key, value) // is called when an option changes // (to be implemented by widget) }; that._self = self; // fixme: remove /*@ bindEvent Binds a function to an event (event, callback) -> This element ({event: callback, ...}) -> This element callback Callback function data event data (key/value pairs) event Event name Event names can be namespaced, like 'click.foo' @*/ that.bindEvent = function() { Ox.forEach(Ox.makeObject(arguments), function(fn, event) { bind('bind', event, fn); }); return that; } /*@ bindEventOnce Binds a function to an event, once (event, callback) -> This element object ({event: callback, ...}) -> This element object callback Callback function data event data (key/value pairs) event Event name Event names can be namespaced, like 'click.foo' @*/ that.bindEventOnce = function() { Ox.forEach(Ox.makeObject(arguments), function(fn, event) { bind('one', event, fn); }); return that; }; /*@ defaults Sets the default options for an element object ({key: value, ...}) -> This element object key The name of the default option value The value of the default option @*/ that.defaults = function(defaults) { // sets the default options self.defaults = defaults; self.options = defaults; return that; }; /*@ gainFocus Makes an element object gain focus () -> This element object @*/ that.gainFocus = function() { Ox.Focus.focus(that.id); return that; }; /*@ hasFocus Returns true if an element object has focus () -> True if the element has focus @*/ that.hasFocus = function() { return Ox.Focus.focused() == that.id; }; /*@ loseFocus Makes an element object lose focus () -> This element object @*/ that.loseFocus = function() { Ox.Focus.blur(that.id); return that; }; /*@ options Gets or sets the options of an element object # Usage () -> all options (key) -> the value of option[key] (key, value) -> this element Sets options[key] to value and calls self.setOption(key, value) if the key/value pair was added or modified ({key: value, ...}) -> this element Sets multiple options and calls self.setOption(key, value) for every key/value pair that was added or modified # Arguments key the name of the option value the value of the option @*/ that.options = function() { return Ox.getset(self.options, arguments, self.setOption, that); }; /*@ removeElement Removes an element object and its event handler () -> This element @*/ that.removeElement = function() { that.loseFocus(); delete self.$eventHandler; that.remove(); // fixme: ok to comment out the following line? delete Ox.UI.elements[that.id]; return that; }; /*@ triggerEvent Triggers an event (event) -> This element object (event, data) -> This element object ({event: data, ...}) -> This element object event Event name data Event data (key/value pairs) @*/ that.triggerEvent = function() { Ox.forEach(Ox.makeObject(arguments), function(data, event) { if ([ 'mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick', 'dragstart', 'drag', 'dragpause', 'dragend', 'playing', 'position', 'progress' ].indexOf(event) == -1) { Ox.print(that.id, self.options.id, 'trigger', event, data); } // 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('ox_' + event, data); }); return that; }; /*@ unbindEvent Unbinds all callbacks from an event To unbind a specific handler, use namespaced events, like bindEvent('click.foo', callback), and then unbindEvent('click.foo'). () -> This element object Unbinds all events (event) -> This element object Unbinds one event (event, event, ...) -> This element object Unbinds multiple events ([event, event, ...]) -> This element object Unbinds multiple events event Event name @*/ that.unbindEvent = function() { if (arguments.length == 0) { self.$eventHandler.unbind(); } else { Ox.makeArray(arguments).forEach(function(event) { self.$eventHandler.unbind('ox_' + event); }); } return that; }; return that; };