1363N/A/*
1363N/A * Copyright 2012 Yahoo! Inc. All rights reserved.
1363N/A * Licensed under the BSD License.
1363N/A * http://yuilibrary.com/license/
1363N/A *
1363N/A * Portions Copyright 2012 Jens Elkner.
1363N/A */
1363N/A
1363N/A// The tooltip in the YUI gallery makes nice effects but is not flexible enough.
1363N/A// So we use a slightly modified version of the YUI examples.
1363N/A// @see http://stage.yuilibrary.com/yui/docs/widget/widget-tooltip.html
1363N/AYUI.add('tooltip', function(Y) {
1363N/A var Lang = Y.Lang, Node = Y.Node;
1363N/A /* Tooltip constructor */
1363N/A function Tooltip(config) {
1363N/A Tooltip.superclass.constructor.apply(this, arguments);
1363N/A };
1363N/A /*
1363N/A * Required NAME static field, used to identify the Widget class and used
1363N/A * as an event prefix, to generate class names etc. (set to the class name
1363N/A * in camel case).
1363N/A */
1363N/A Tooltip.NAME = 'tooltip';
1363N/A /* Static constants */
1363N/A Tooltip.OFFSET_X = 15;
1363N/A Tooltip.OFFSET_Y = 15;
1363N/A Tooltip.OFFSCREEN_X = -10000;
1363N/A Tooltip.OFFSCREEN_Y = -10000;
1363N/A /* Default Tooltip Attributes */
1363N/A Tooltip.ATTRS = {
1363N/A /*
1363N/A * The tooltip content. This can either be a String, Node, or a simple
1363N/A * map of id-to-values, designed to be used when a single tooltip is
1363N/A * mapped to multiple trigger elements.
1363N/A */
1363N/A content : {
1363N/A value : null
1363N/A },
1363N/A /*
1363N/A * The set of nodes to bind to the tooltip instance. Can be a string,
1363N/A * or a node instance.
1363N/A */
1363N/A triggerNodes : {
1363N/A value : null,
1363N/A setter : function(val) {
1363N/A if (val && Lang.isString(val)) {
1363N/A val = Node.all(val);
1363N/A }
1363N/A return val;
1363N/A }
1363N/A },
1363N/A /*
1363N/A * The delegate node to which event listeners should be attached. This
1363N/A * node should be an ancestor of all trigger nodes bound to the
1363N/A * instance. By default the document is used.
1363N/A */
1363N/A delegate : {
1363N/A value : null,
1363N/A setter : function(val) {
1363N/A return Y.one(val) || Y.one('document');
1363N/A }
1363N/A },
1363N/A /*
1363N/A * The time to wait, after the mouse enters the trigger node, to display
1363N/A * the tooltip
1363N/A */
1363N/A showDelay : {
1363N/A value : 250
1363N/A },
1363N/A /*
1363N/A * The time to wait, after the mouse leaves the trigger node, to hide
1363N/A * the tooltip
1363N/A */
1363N/A hideDelay : {
1363N/A value : 10
1363N/A },
1363N/A /*
1363N/A * The time to wait, after the tooltip is first displayed for a trigger
1363N/A * node, to hide it, if the mouse has not left the trigger node
1363N/A */
1363N/A autoHideDelay : {
1363N/A value : 2000
1363N/A },
1363N/A /*
1363N/A * Override the default visibility set by the widget base class
1363N/A */
1363N/A visible : {
1363N/A value : false
1363N/A },
1363N/A /*
1363N/A * Override the default XY value set by the widget base class, to
1363N/A * position the tooltip offscreen
1363N/A */
1363N/A xy : {
1363N/A value : [ Tooltip.OFFSCREEN_X, Tooltip.OFFSCREEN_Y ]
1363N/A }
1363N/A };
1363N/A
1363N/A /* Extend the base Widget class */
1363N/A Y.extend(Tooltip, Y.Widget, {
1363N/A /*
1363N/A * Initialization Code: Sets up privately used state properties, and
1363N/A * publishes the events Tooltip introduces
1363N/A */
1363N/A initializer : function(config) {
1363N/A this._triggerClassName = this.getClassName('trigger');
1363N/A // Currently bound trigger node information
1363N/A this._currTrigger = {
1363N/A node : null,
1363N/A title : null,
1363N/A mouseX : Tooltip.OFFSCREEN_X,
1363N/A mouseY : Tooltip.OFFSCREEN_Y
1363N/A };
1363N/A // Event handles - mouse over is set on the delegate element,
1363N/A // mousemove and mouseout are set on the trigger node
1363N/A this._eventHandles = {
1363N/A delegate : null,
1363N/A trigger : {
1363N/A mouseMove : null,
1363N/A mouseOut : null
1363N/A }
1363N/A };
1363N/A // Show/hide timers
1363N/A this._timers = {
1363N/A show : null,
1363N/A hide : null
1363N/A };
1363N/A // Publish events introduced by Tooltip. Note the triggerEnter event
1363N/A // is preventable, with the default behavior defined in the
1363N/A // _defTriggerEnterFn method
1363N/A this.publish('triggerEnter', {
1363N/A defaultFn : this._defTriggerEnterFn,
1363N/A preventable : true
1363N/A });
1363N/A this.publish('triggerLeave', {
1363N/A preventable : false
1363N/A });
1363N/A // we assume, horizontal scrollbar has ~ same height as vert. and
1363N/A // doesn't change
1363N/A this.sbWidth = Y.DOM.getScrollbarWidth();
1363N/A },
1363N/A /*
1363N/A * Destruction Code: Clears event handles, timers, and current trigger
1363N/A * information
1363N/A */
1363N/A destructor : function() {
1363N/A this._clearCurrentTrigger();
1363N/A this._clearTimers();
1363N/A this._clearHandles();
1363N/A },
1363N/A /*
1363N/A * bindUI is used to bind attribute change and dom event listeners
1363N/A */
1363N/A bindUI : function() {
1363N/A this.after('delegateChange', this._afterSetDelegate);
1363N/A this.after('nodesChange', this._afterSetNodes);
1363N/A this._bindDelegate();
1363N/A },
1363N/A /*
1363N/A * syncUI is used to update the rendered DOM, based on the current
1363N/A * Tooltip state
1363N/A */
1363N/A syncUI : function() {
1363N/A this._uiSetNodes(this.get('triggerNodes'));
1363N/A },
1363N/A /*
1363N/A * Helper method to extract the content for the tooltip of the given
1363N/A * node, based on (in order of precedence):
1363N/A *
1363N/A * a). The given node's 'content' attribute value, if set:
1363N/A * 0) If its value denotes a function, the result of executing as
1363N/A * function(node, this), whereby this is the Tooltip instance
1363N/A * itself.
1363N/A * 1) If the value is a map, the maps value for the key equal to
1363N/A * the node's Id.
1363N/A * 2) If the value is a String or Node, the value itself.
1363N/A * 3) otherwise, fallback to b)
1363N/A * b) The value of the node's 'title' attribute.
1363N/A *
1363N/A * @return either an innerHTML acceptable String or a Node
1363N/A */
1363N/A getTooltipContent : function(node) {
1363N/A var content = this.get('content');
1363N/A if (content) {
1363N/A if (Lang.isFunction(content)) {
1363N/A content = content(node, this);
1363N/A } else if (Y.Lang.isArray(content)) {
1363N/A content = content[node.get('id')];
1363N/A } else if (!(content instanceof Node || Y.Lang.isString(content))) {
1363N/A content = null;
1363N/A }
1363N/A }
1363N/A if (!content) {
1363N/A content = node.getAttribute('title');
1363N/A }
1363N/A return content;
1363N/A },
1363N/A /*
1363N/A * Public method, which can be used by triggerEvent event listeners to
1363N/A * set the content of the tooltip for the current trigger node. This
1363N/A * implementation uses {#getTooltipContent} to extract the content to
1363N/A * show.
1363N/A */
1363N/A setTriggerContent : function(node) {
1384N/A var l, content = this.getTooltipContent(node),
1384N/A contentBox = this.get('contentBox');
1363N/A contentBox.set('innerHTML', '');
1363N/A if (content) {
1363N/A if (content instanceof Node) {
1384N/A contentBox.appendChild(content);
1363N/A } else if (Lang.isString(content)) {
1363N/A contentBox.set('innerHTML',content);
1363N/A }
1363N/A }
1363N/A },
1363N/A /*
1363N/A * Gets the closest ancestor of the given node, which is a tooltip
1363N/A * trigger node
1363N/A */
1363N/A getParentTrigger : function(node) {
1363N/A var cn = this._triggerClassName;
1363N/A return (node.hasClass(cn))
1363N/A ? node
1363N/A : node.ancestor(function(node) { return node.hasClass(cn); });
1363N/A },
1363N/A /*
1363N/A * Default attribute change listener for the triggerNodes attribute
1363N/A */
1363N/A _afterSetNodes : function(e) {
1363N/A this._uiSetNodes(e.newVal);
1363N/A },
1363N/A /*
1363N/A * Default attribute change listener for the delegate attribute
1363N/A */
1363N/A _afterSetDelegate : function(e) {
1363N/A this._bindDelegate(e.newVal);
1363N/A },
1363N/A /*
1363N/A * Updates the rendered DOM to reflect the set of trigger nodes passed in
1363N/A */
1363N/A _uiSetNodes : function(nodes) {
1363N/A if (this._triggerNodes) {
1363N/A this._triggerNodes.removeClass(this._triggerClassName);
1363N/A }
1363N/A if (nodes) {
1363N/A this._triggerNodes = nodes;
1363N/A this._triggerNodes.addClass(this._triggerClassName);
1363N/A }
1363N/A },
1363N/A /*
1363N/A * Attaches the default mouseover DOM listener to the current delegate node
1363N/A */
1363N/A _bindDelegate : function() {
1363N/A var eventHandles = this._eventHandles;
1363N/A if (eventHandles.delegate) {
1363N/A eventHandles.delegate.detach();
1363N/A eventHandles.delegate = null;
1363N/A }
1363N/A eventHandles.delegate = Y.on('mouseover',
1363N/A Y.bind(this._onDelegateMouseOver, this), this.get('delegate'));
1363N/A },
1363N/A /*
1363N/A * Default mouse over DOM event listener.
1363N/A *
1363N/A * Delegates to the _enterTrigger method, if the mouseover enters a
1363N/A * trigger node.
1363N/A */
1363N/A _onDelegateMouseOver : function(e) {
1363N/A var node = this.getParentTrigger(e.target);
1363N/A if (node && (!this._currTrigger.node
1363N/A || !node.compareTo(this._currTrigger.node)))
1363N/A {
1363N/A this._enterTrigger(node, e.pageX, e.pageY);
1363N/A }
1363N/A },
1363N/A /*
1363N/A * Default mouse out DOM event listener
1363N/A *
1363N/A * Delegates to _leaveTrigger if the mouseout leaves the current trigger
1363N/A * node
1363N/A */
1363N/A _onNodeMouseOut : function(e) {
1363N/A var to = e.relatedTarget;
1363N/A var trigger = e.currentTarget;
1363N/A if (!trigger.contains(to)) {
1363N/A this._leaveTrigger(trigger);
1363N/A }
1363N/A },
1363N/A /*
1363N/A * Default mouse move DOM event listener
1363N/A */
1363N/A _onNodeMouseMove : function(e) {
1363N/A this._overTrigger(e.pageX, e.pageY);
1363N/A },
1363N/A /*
1363N/A * Default handler invoked when the mouse enters a trigger node. Set the
1363N/A * content of the tooltip box and fires the triggerEnter event, which
1363N/A * inturn notifies all listeners. Listeners may prevent the tooltip
1363N/A * from being displayed.
1363N/A */
1363N/A _enterTrigger : function(node, x, y) {
1363N/A this._setCurrentTrigger(node, x, y);
1363N/A this.fire('triggerEnter', {
1363N/A node : node,
1363N/A pageX : x,
1363N/A pageY : y
1363N/A });
1363N/A },
1363N/A /*
1363N/A * Default handler for the triggerEvent event, which will setup the
1363N/A * timer to display the tooltip, if the default handler has not been
1363N/A * prevented.
1363N/A */
1363N/A _defTriggerEnterFn : function(e) {
1363N/A var node = e.node;
1363N/A if (!this.get('disabled')) {
1363N/A this._clearTimers();
1363N/A var delay = (this.get('visible')) ? 0 : this.get('showDelay');
1363N/A this._timers.show =
1363N/A Y.later(delay, this, this._showTooltip, [ node ]);
1363N/A }
1363N/A },
1363N/A /*
1363N/A * Default handler invoked when the mouse leaves the current trigger
1363N/A * node. Fires the triggerLeave event and sets up the hide timer
1363N/A */
1363N/A _leaveTrigger : function(node) {
1363N/A this.fire('triggerLeave');
1363N/A this._clearCurrentTrigger();
1363N/A this._clearTimers();
1363N/A this._timers.hide =
1363N/A Y.later(this.get('hideDelay'), this, this._hideTooltip);
1363N/A },
1363N/A /*
1363N/A * Default handler invoked for mousemove events on the trigger node.
1363N/A * Stores the current mouse x, y positions
1363N/A */
1363N/A _overTrigger : function(x, y) {
1363N/A this._currTrigger.mouseX = x;
1363N/A this._currTrigger.mouseY = y;
1363N/A },
1363N/A /*
1363N/A * Shows the tooltip, after moving it to the current mouse position.
1363N/A */
1363N/A _showTooltip : function(node) {
1363N/A var x = this._currTrigger.mouseX + Tooltip.OFFSET_X;
1363N/A var y = this._currTrigger.mouseY + Tooltip.OFFSET_Y;
1363N/A var cn = this.get('contentBox');
1363N/A var max = Y.DOM.winHeight() - cn.get('clientHeight') - this.sbWidth;
1363N/A if (y > max) {
1363N/A y = max;
1363N/A }
1363N/A max = Y.DOM.winWidth() - cn.get('clientWidth') - this.sbWidth;
1363N/A if (x > max) {
1363N/A x = max;
1363N/A }
1363N/A this.move(x, y);
1363N/A this.show();
1363N/A this._clearTimers();
1363N/A this._timers.hide =
1363N/A Y.later(this.get('autoHideDelay'), this, this._hideTooltip);
1363N/A },
1363N/A /*
1363N/A * Hides the tooltip, after clearing existing timers.
1363N/A */
1363N/A _hideTooltip : function() {
1363N/A this._clearTimers();
1363N/A this.hide();
1363N/A },
1363N/A /*
1363N/A * Set the currently bound trigger node information, clearing out the
1363N/A * title attribute if set and setting up mousemove/out listeners.
1363N/A */
1363N/A _setCurrentTrigger : function(node, x, y) {
1363N/A var currTrigger = this._currTrigger, triggerHandles
1363N/A = this._eventHandles.trigger;
1363N/A this.setTriggerContent(node);
1363N/A triggerHandles.mouseMove =
1363N/A Y.on('mousemove', Y.bind(this._onNodeMouseMove, this), node);
1363N/A triggerHandles.mouseOut =
1363N/A Y.on('mouseout', Y.bind(this._onNodeMouseOut, this), node);
1363N/A var title = node.getAttribute('title');
1363N/A if (title) {
1363N/A node.setAttribute('title', '');
1363N/A }
1363N/A currTrigger.mouseX = x;
1363N/A currTrigger.mouseY = y;
1363N/A currTrigger.node = node;
1363N/A currTrigger.title = title;
1363N/A },
1363N/A /*
1363N/A * Clear out the current trigger state, restoring the title attribute
1363N/A * on the trigger node, if it was originally set.
1363N/A */
1363N/A _clearCurrentTrigger : function() {
1363N/A var currTrigger = this._currTrigger, triggerHandles =
1363N/A this._eventHandles.trigger;
1363N/A if (currTrigger.node) {
1363N/A var node = currTrigger.node;
1363N/A var title = currTrigger.title || '';
1363N/A currTrigger.node = null;
1363N/A currTrigger.title = '';
1363N/A triggerHandles.mouseMove.detach();
1363N/A triggerHandles.mouseOut.detach();
1363N/A triggerHandles.mouseMove = null;
1363N/A triggerHandles.mouseOut = null;
1363N/A if (title) {
1363N/A node.setAttribute('title', title);
1363N/A }
1363N/A }
1363N/A },
1363N/A /*
1363N/A * Cancel any existing show/hide timers
1363N/A */
1363N/A _clearTimers : function() {
1363N/A var timers = this._timers;
1363N/A if (timers.hide) {
1363N/A timers.hide.cancel();
1363N/A timers.hide = null;
1363N/A }
1363N/A if (timers.show) {
1363N/A timers.show.cancel();
1363N/A timers.show = null;
1363N/A }
1363N/A },
1363N/A /*
1363N/A * Detach any stored event handles
1363N/A */
1363N/A _clearHandles : function() {
1363N/A var eventHandles = this._eventHandles;
1363N/A if (eventHandles.delegate) {
1363N/A this._eventHandles.delegate.detach();
1363N/A }
1363N/A if (eventHandles.trigger.mouseOut) {
1363N/A eventHandles.trigger.mouseOut.detach();
1363N/A }
1363N/A if (eventHandles.trigger.mouseMove) {
1363N/A eventHandles.trigger.mouseMove.detach();
1363N/A }
1363N/A }
1363N/A });
1363N/A // dynamic:false = Modify the existing Tooltip class
1363N/A Y.Tooltip = Y.Base.build(Tooltip.NAME, Tooltip,
1363N/A [ Y.WidgetPosition, Y.WidgetStack ],
1363N/A { dynamic : false } );
1363N/A}, '1.0', { requires: ['widget', 'widget-position', 'widget-stack']});
1363N/A// vim: set filetype=javascript ts=4