/** * 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 * }); * * }, * destroy: function(me) { * Ext.tip.QuickTipManager.unregister(me.getId()); * } * } * }); * */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: ' ', /** * @cfg checkNestedDelegates * @inheritdoc */ checkNestedDelegates: true, /** * @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' }, isQuickTip: true, /** * @cfg shrinkWrapDock * @inheritdoc */ shrinkWrapDock: true, initComponent: function() { var me = this; // delegate selector is a function which detects presence // of attributes which provide QuickTip text. me.delegate = me.delegate.bind(me); me.target = me.target || Ext.getDoc(); me.targets = me.targets || {}; me.header = me.header || {}; me.header.focusableContainer = false; 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 Ext.tip.QuickTip#target}. * @param config.text Tip body content. See {@link Ext.tip.QuickTip#text}. * @param config.title Tip header. See {@link Ext.tip.QuickTip#title}. * @param config.autoHide False to prevent the tip from automatically hiding on * mouseleave. See {@link Ext.tip.QuickTip#autoHide}. * @param config.cls An optional extra CSS class that will be added to the tip. See * {@link Ext.tip.QuickTip#cls}. * @param config.dismissDelay Delay in milliseconds before the tooltip automatically * hides (overrides singleton value). See {@link Ext.tip.QuickTip#dismissDelay}. * @param config.width Tip width in pixels. See {@link Ext.tip.QuickTip#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, currentTarget = me.currentTarget; el = Ext.getDom(el); if (me.isVisible()) { if (currentTarget.dom === el) { me.hide(); } } else if (currentTarget.dom === el) { me.clearTimer('show'); } }, delegate: function(target) { var me = this, cfg = me.tagConfig, attr = cfg.attr || (cfg.attr = cfg.namespace + cfg.attribute), registeredTarget = me.targets[target.id], text; // We can now only activate on elements which have the required attributes text = target.getAttribute(attr) || (me.interceptTitles && target.title) || (registeredTarget && registeredTarget.text); return !!text; }, /** * @private * Reads the tip text from the target. */ getTipText: function(target) { var titleText = target.title, cfg = this.tagConfig, attr = cfg.attr || (cfg.attr = cfg.namespace + cfg.attribute); if (this.interceptTitles && titleText) { target.setAttribute(attr, titleText); target.removeAttribute('title'); return titleText; } else { return target.getAttribute(attr); } }, onTargetOver: function(event) { var me = this, currentTarget = me.currentTarget, target = event.target, targets, registeredTarget, key; // If the over target 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; } me.pointerEvent = event; targets = me.targets; // Loop through registered targets seeing if we are over one. for (key in targets) { if (targets.hasOwnProperty(key)) { registeredTarget = targets[key]; target = Ext.getDom(registeredTarget.target); // If we moved over a registered target from outside of it, activate it. if (target && Ext.fly(target).contains(event.target) && !Ext.fly(target).contains(event.relatedTarget)) { currentTarget.attach(target); me.activeTarget = registeredTarget; registeredTarget.el = currentTarget; me.anchor = registeredTarget.anchor; me.activateTarget(); return; } } } // We found no registered targets, now continue as a regular ToolTip, and // see if we are over any of our delegated targets. me.callParent([event]); }, handleTargetOver: function(target, event) { var me = this, currentTarget = me.currentTarget, cfg = me.tagConfig, ns = cfg.namespace, tipText = me.getTipText(target, event), autoHide; if (tipText) { autoHide = currentTarget.getAttribute(ns + cfg.hide); me.activeTarget = { el: currentTarget, text: tipText, width: +currentTarget.getAttribute(ns + cfg.width) || null, autoHide: autoHide !== "user" && autoHide !== 'false', title: currentTarget.getAttribute(ns + cfg.title), cls: currentTarget.getAttribute(ns + cfg.cls), align: currentTarget.getAttribute(ns + cfg.align), showDelay: currentTarget.getAttribute(ns + cfg.showDelay), hideAction: currentTarget.getAttribute(ns + cfg.hideAction), alignTarget: currentTarget.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 = currentTarget.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.alignTarget = me.activeTarget.alignTarget || target; } me.activateTarget(); } }, activateTarget: function() { var me = this, activeTarget = me.activeTarget, delay = activeTarget.showDelay, hideAction = activeTarget.hideAction; // If moved from target to target rapidly, the hide delay will not // have fired, so just update content and alignment. if (me.isVisible()) { me.updateContent(); me.realignToTarget(); } else { if (activeTarget.showDelay) { delay = me.showDelay; me.showDelay = parseInt(activeTarget.showDelay, 10); } me.delayShow(); if (activeTarget.showDelay) { me.showDelay = delay; } if (!(hideAction = activeTarget.hideAction)) { delete me.hideAction; } else { me.hideAction = hideAction; } } }, getAnchorAlign: function() { var active = this.activeTarget; return (active && active.align) || this.callParent(); }, getAlignRegion: function() { var me = this, activeTarget = me.activeTarget, currentTargetDom = me.currentTarget.dom, result; // If we are anchored, and not configured with an anchorTarget, // align to the target element, or whatever its 'data-anchortarget' points to if (activeTarget && activeTarget.alignTarget && me.anchor && !me.initialConfig.hasOwnProperty('anchorTarget')) { me.currentTarget.attach(Ext.getDom(activeTarget.alignTarget)); } // Anchor to the target when have an align config or an anchor config me.anchorToTarget = !!(activeTarget.align || me.anchor); result = me.callParent(); // Return currentTarget to correctness for pointer event processing me.currentTarget.attach(currentTargetDom); return result; }, /** * @private */ handleTargetOut: function(e) { var me = this, active = me.activeTarget, autoHide = me.autoHide, hideDelay = me.hideDelay; if (active && autoHide !== false) { me.autoHide = true; if (active.hideDelay) { me.hideDelay = parseInt(active.hideDelay, 10); } me.callParent([e]); me.autoHide = autoHide; me.hideDelay = hideDelay; } }, 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.el.dom)]) { 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.activeTarget = null; me.currentTarget.detach(); return; } me.callParent(arguments); }, /** * @method beforeShow * @inheritdoc Ext.tip.Tip#method-beforeShow */ beforeShow: function() { this.updateContent(); this.callParent(arguments); }, /** * @private */ updateContent: function() { var me = this, target = me.activeTarget, header = me.header, dismiss, cls; if (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; 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); me.align = target.align; me.resumeLayouts(true); } }, /** * @method hide * @inheritdoc */ hide: function() { this.activeTarget = null; this.callParent(); }});