// 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 <function:Ox.JQueryElement> Basic UI element object # Usage -------------------------------------------------------------------- ([options[, self]]) -> <object> UI element # Arguments ---------------------------------------------------------------- options <object> the options of the element # Properties element <string> tagname or CSS selector tooltip <string|function> tooltip title or function that returns one (e) -> <string> tooltip title e <object> mouse event options <string> tagname or CSS selector self <object> shared private variable # Events ------------------------------------------------------------------- anyclick <event> anyclick fires on mouseup, but not on any subsequent mouseup within 250 ms * <*> original event properties doubleclick <event> doubleclick fires on the second mousedown within 250 ms * <*> original event properties drag <event> drag fires on mousemove after dragstart, stops firing on mouseup clientDX <number> horizontal drag delta in px clientDY <number> vertical drag delta in px * <*> original event properties dragend <event> dragpause Fires on mouseup after dragstart clientDX <number> horizontal drag delta in px clientDY <number> vertical drag delta in px * <*> original event properties dragpause <event> dragpause Fires once when the mouse doesn't move for 250 ms after drag clientDX <number> horizontal drag delta in px clientDY <number> vertical drag delta in px * <*> original event properties dragstart <event> dragstart fires when the mouse is down for 250 ms * <*> original event properties mouserepeat <event> mouserepeat fires every 50 ms after the mouse was down for 250 ms, stops firing on mouseleave or mouseup * <*> original event properties singleclick <event> 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('<tagname>') // or Ox.TestElement('cssSelector') if (Ox.isString(self.options)) { self.options = { element: self.options }; } // create event handler if (!self.$eventHandler) { self.$eventHandler = $('<div>'); } // create public object var that = new Ox.JQueryElement( $(self.options.element || '<div>') ) .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 <function> Binds a function to an event (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 <code>'click.foo'</code> @*/ that.bindEvent = function() { Ox.forEach(Ox.makeObject(arguments), function(fn, event) { bind('bind', event, fn); }); return that; } /*@ 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 <code>'click.foo'</code> @*/ that.bindEventOnce = function() { Ox.forEach(Ox.makeObject(arguments), function(fn, event) { bind('one', event, fn); }); return that; }; /*@ defaults <function> Sets the default options for an element object ({key: value, ...}) -> <obj> This element object key <str> The name of the default option value <val> The value of the default option @*/ that.defaults = function(defaults) { // sets the default options self.defaults = defaults; self.options = defaults; return that; }; /*@ gainFocus <function> Makes an element object gain focus () -> <obj> This element object @*/ that.gainFocus = function() { Ox.Focus.focus(that.id); return that; }; /*@ 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.id; }; /*@ loseFocus <function> Makes an element object lose focus () -> <object> This element object @*/ that.loseFocus = function() { Ox.Focus.blur(that.id); return that; }; /*@ options <function> Gets or sets the options of an element object # Usage () -> <obj> all options (key) -> <any> the value of option[key] (key, value) -> <obj> this element Sets options[key] to value and calls self.setOption(key, value) if the key/value pair was added or modified ({key: value, ...}) -> <obj> this element Sets multiple options and calls self.setOption(key, value) for every key/value pair that was added or modified # Arguments key <str> the name of the option value <val> the value of the option @*/ that.options = function() { return Ox.getset(self.options, arguments, self.setOption, that); }; /*@ removeElement <function> Removes an element object and its event handler () -> <obj> This element @*/ that.removeElement = function() { that.loseFocus(); delete self.$eventHandler; that.remove(); delete Ox.UI.elements[that.id]; return that; }; /*@ 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) { 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 <function> Unbinds all callbacks from an event To unbind a specific handler, use namespaced events, like <code>bindEvent('click.foo', callback)</code>, and then <code>unbindEvent('click.foo')</code>. () -> <object> This element object Unbinds all events (event) -> <object> This element object Unbinds one event (event, event, ...) -> <object> This element object Unbinds multiple events ([event, event, ...]) -> <object> This element object Unbinds multiple events event <string> 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; };