/** * A specialized tooltip class for tooltips that can be specified in markup and automatically managed * by the global {@link Ext.tip.QuickTipManager} instance. See the QuickTipManager documentation for * additional usage details and examples. * * @example * Ext.tip.QuickTipManager.init(); // Instantiate the QuickTipManager * * Ext.create('Ext.Button', { * * renderTo: Ext.getBody(), * text: 'My Button', * listeners: { * * afterrender: function(me) { * * // Register the new tip with an element's ID * Ext.tip.QuickTipManager.register({ * target: me.getId(), // Target button's ID * title : 'My Tooltip', // QuickTip Header * text : 'My Button has a QuickTip' // Tip content * }); * * } * } * }); * */Ext.define('Ext.tip.QuickTip', { extend: 'Ext.tip.ToolTip', alias: 'widget.quicktip', alternateClassName: 'Ext.QuickTip', /** * @cfg {String/HTMLElement/Ext.dom.Element} target * The target HTMLElement, {@link Ext.dom.Element} or id to associate with this Quicktip. * * Defaults to the document. */ /** * @cfg {Boolean} interceptTitles * `true` to automatically use the element's DOM title value if available. */ interceptTitles : false, /** * @cfg {String/Ext.panel.Title} title * The title text to be used to display in the Tip header. May be a string * (including HTML tags) or an {@link Ext.panel.Title} config object. */ title: ' ', /** * @private */ tagConfig : { namespace : 'data-', attribute : 'qtip', width : 'qwidth', target : 'target', title : 'qtitle', hide : 'hide', cls : 'qclass', align : 'qalign', anchor : 'anchor', showDelay: 'qshowDelay', hideAction: 'hideAction', anchorTarget: 'anchorTarget' }, shrinkWrapDock: true, initComponent : function(){ var me = this; me.target = me.target || Ext.getDoc(); me.targets = me.targets || {}; me.callParent(); }, setTagConfig: function(cfg) { this.tagConfig = Ext.apply({}, cfg); // Let attr get recomputed delete this.tagConfig.attr; }, /** * @cfg text * @inheritdoc Ext.tip.ToolTip#cfg-html */ text: null, /** * @cfg html * @hide * -- hidden for Ext.tip.QuickTip - see #cfg-text */ /** * Configures a new quick tip instance and assigns it to a target element. * * For example usage, see the {@link Ext.tip.QuickTipManager} class header. * * @param {Object} config The config object with the following properties: * @param config.target (required) The target HTMLElement, {@link Ext.dom.Element} or * id to associate with this Quicktip. See {@link #target}. * @param config.text Tip body content. See {@link #text}. * @param config.title Tip header. See {@link #title}. * @param config.autoHide False to prevent the tip from automatically hiding on * mouseleave. See {@link #autoHide}. * @param config.cls An optional extra CSS class that will be added to the tip. See * {@link #cls}. * @param config.dismissDelay Delay in milliseconds before the tooltip automatically * hides (overrides singleton value). See {@link #dismissDelay}. * @param config.width Tip width in pixels. See {@link #width}. */ register : function(config){ var configs = Ext.isArray(config) ? config : arguments, i = 0, len = configs.length, target, j, targetLen; for (; i < len; i++) { config = configs[i]; target = config.target; if (target) { if (Ext.isArray(target)) { for (j = 0, targetLen = target.length; j < targetLen; j++) { this.targets[Ext.id(target[j])] = config; } } else{ this.targets[Ext.id(target)] = config; } } } }, /** * Removes this quick tip from its element and destroys it. * @param {String/HTMLElement/Ext.dom.Element} el The element from which the quick tip * is to be removed or ID of the element. */ unregister : function(el){ delete this.targets[Ext.id(el)]; }, /** * Hides a visible tip or cancels an impending show for a particular element. * @param {String/HTMLElement/Ext.dom.Element} el The element that is the target of * the tip or ID of the element. */ cancelShow: function(el){ var me = this, activeTarget = me.activeTarget; el = Ext.get(el).dom; if (me.isVisible()) { if (activeTarget && activeTarget.el === el) { me.hide(); } } else if (activeTarget && activeTarget.el === el) { me.clearTimer('show'); } }, /** * @private * Reads the tip text from the closest node to the event target which contains the * attribute we are configured to look for. Returns an object containing the text * from the attribute, and the target element from which the text was read. */ getTipCfg: function (target, event) { var titleText = target.title, cfg = this.tagConfig, attr = cfg.attr || (cfg.attr = cfg.namespace + cfg.attribute), text; if (this.interceptTitles && titleText && Ext.isString(titleText)) { target.setAttribute(attr, titleText); target.removeAttribute('title'); return { text: titleText }; } else { target = Ext.fly(target).findParent(function(dom) { // Want to test the truthiness of the attribute and save it. return (text = dom.getAttribute(attr)); }); if (target) { return { target: target, text: text }; } } }, onTargetOver: function (event) { // This is a separate method so that tooltip activation can be done by calling // doTargetOver directly. this.doTargetOver(event.getTarget(this.delegate), null, event); }, /** * @private */ doTargetOver : function (target, xy, event) { var me = this, hasShowDelay, hideAction, delay, elTarget, cfg, ns, tipConfig, autoHide, targets, targetEl, value, key; if (me.disabled) { return; } if (typeof target === 'string') { target = Ext.getDom(target); } me.targetXY = xy || (event ? event.getXY() : Ext.fly(target).getXY()); // If the over target was filtered out by the delegate selector, or is not an HTMLElement, or is the <html> or the <body>, then return if(!target || target.nodeType !== 1 || target === document.documentElement || target === document.body){ return; } if (me.activeTarget && ((target === me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) { // We may have started a delayed show where we have an active target. // If the timer is yet to be fired, but the mouse moves, it will try to // show it immediately. If the attribute has been removed from the element, // we want to cancel the show. if (me.targetTextEmpty()) { me.onShowVeto(); delete me.activeTarget; } else { me.clearTimer('hide'); me.show(); } return; } if (target) { targets = me.targets; for (key in targets) { if (targets.hasOwnProperty(key)) { value = targets[key]; targetEl = Ext.fly(value.target); if (targetEl && (targetEl.dom === target || targetEl.contains(target))) { elTarget = targetEl.dom; break; } } } if (elTarget) { me.activeTarget = me.targets[elTarget.id]; me.activeTarget.el = target; me.anchor = me.activeTarget.anchor; if (me.anchor) { me.anchorTarget = target; } hasShowDelay = parseInt(me.activeTarget.showDelay, 10); if (hasShowDelay) { delay = me.showDelay; me.showDelay = hasShowDelay; } me.delayShow(); if (hasShowDelay) { me.showDelay = delay; } if (!(hideAction = me.activeTarget.hideAction)) { delete me.hideAction; } else { me.hideAction = hideAction; } return; } } // Should be a fly. elTarget = Ext.fly(target, '_quicktip-target'); cfg = me.tagConfig; ns = cfg.namespace; tipConfig = me.getTipCfg(target, event); if (tipConfig) { // getTipCfg may look up the parentNode axis for a tip text attribute and will return the new target node. // Change our target element to match that from which the tip text attribute was read. if (tipConfig.target) { target = tipConfig.target; elTarget = Ext.fly(target, '_quicktip-target'); } autoHide = elTarget.getAttribute(ns + cfg.hide); me.activeTarget = { el: target, text: tipConfig.text, width: +elTarget.getAttribute(ns + cfg.width) || null, autoHide: autoHide !== "user" && autoHide !== 'false', title: elTarget.getAttribute(ns + cfg.title), cls: elTarget.getAttribute(ns + cfg.cls), align: elTarget.getAttribute(ns + cfg.align), showDelay: parseInt(elTarget.getAttribute(ns + cfg.showDelay)||0, 10), hideAction: elTarget.getAttribute(ns + cfg.hideAction), anchorTarget: elTarget.getAttribute(ns + cfg.anchorTarget) }; // If we were not configured with an anchor, allow it to be set by the target's properties if (!me.initialConfig.hasOwnProperty('anchor')) { me.anchor = elTarget.getAttribute(ns + cfg.anchor); } // If we are anchored, and not configured with an anchorTarget, anchor to the target element, or whatever its 'data-anchortarget' points to if (me.anchor && !me.initialConfig.hasOwnProperty('anchorTarget')) { me.anchorTarget = me.activeTarget.anchorTarget || target; } hasShowDelay = parseInt(me.activeTarget.showDelay, 10); if (hasShowDelay) { delay = me.showDelay; me.showDelay = hasShowDelay; } me.delayShow(); if (hasShowDelay) { me.showDelay = delay; } } }, /** * @private */ onTargetOut : function(e){ var me = this, active = me.activeTarget, hasHideDelay, delay; // If moving within the current target, and it does not have a new tip, ignore // the mouseout. e.within is the only correct way to determine this. if (active && e.within(me.activeTarget.el) && !me.getTipCfg(e.getTarget(), e)) { return; } me.clearTimer('show'); delete me.activeTarget; if (me.autoHide !== false) { hasHideDelay = active && parseInt(active.hideDelay, 10); if (hasHideDelay) { delay = me.hideDelay; me.hideDelay = hasHideDelay; } me.delayHide(); if (hasHideDelay) { me.hideDelay = delay; } } }, targetTextEmpty: function(){ var me = this, target = me.activeTarget, cfg = me.tagConfig, el, text; if (target) { el = target.el; if (el) { text = el.getAttribute(cfg.namespace + cfg.attribute); // Note that the quicktip could also have been registered with the QuickTipManager. // If this was the case, then we don't want to veto showing it. // Simply do a lookup in the registered targets collection. if (!text && !me.targets[Ext.id(target.target)]) { return true; } } } return false; }, show: function(){ var me = this, fromDelay = me.fromDelayShow; // We're coming from a delayed show, so check whether // the attribute has been removed before we show it if (fromDelay && me.targetTextEmpty()) { me.onShowVeto(); delete me.activeTarget; return; } me.callParent(arguments); }, /** * @inheritdoc Ext.tip.Tip#method-showAt */ showAt : function(xy){ var me = this, target = me.activeTarget, header = me.header, dismiss, cls; if (target) { if (!me.rendered) { me.render(Ext.getBody()); me.activeTarget = target; } me.suspendLayouts(); if (target.title) { me.setTitle(target.title); header.show(); } else if (header) { header.hide(); } me.update(target.text); me.autoHide = target.autoHide; dismiss = target.dismissDelay; me.dismissDelay = Ext.isNumber(dismiss) ? dismiss : me.dismissDelay; if (target.mouseOffset) { xy[0] += target.mouseOffset[0]; xy[1] += target.mouseOffset[1]; } cls = me.lastCls; if (cls) { me.removeCls(cls); delete me.lastCls; } cls = target.cls; if (cls) { me.addCls(cls); me.lastCls = cls; } me.setWidth(target.width); if (me.anchor) { me.constrainPosition = false; } else if (target.align) { // TODO: this doesn't seem to work consistently xy = me.getAlignToXY(target.el, target.align); me.constrainPosition = false; }else{ me.constrainPosition = true; } me.resumeLayouts(true); } me.callParent([xy]); }, /** * @method hide * @inheritdoc */ hide: function(){ delete this.activeTarget; this.callParent(); }});