Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / tip / QuickTip.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.tip.QuickTip
17  * @extends Ext.tip.ToolTip
18  * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
19  * {@link Ext.tip.QuickTipManager} instance.  See the QuickTipManager class header for additional usage details and examples.
20  * @xtype quicktip
21  */
22 Ext.define('Ext.tip.QuickTip', {
23     extend: 'Ext.tip.ToolTip',
24     alternateClassName: 'Ext.QuickTip',
25     /**
26      * @cfg {Mixed} target The target HTMLElement, Ext.core.Element or id to associate with this Quicktip (defaults to the document).
27      */
28     /**
29      * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available (defaults to false).
30      */
31     interceptTitles : false,
32
33     // Force creation of header Component
34     title: ' ',
35
36     // private
37     tagConfig : {
38         namespace : "data-",
39         attribute : "qtip",
40         width : "qwidth",
41         target : "target",
42         title : "qtitle",
43         hide : "hide",
44         cls : "qclass",
45         align : "qalign",
46         anchor : "anchor"
47     },
48
49     // private
50     initComponent : function(){
51         var me = this;
52         
53         me.target = me.target || Ext.getDoc();
54         me.targets = me.targets || {};
55         me.callParent();
56     },
57
58     /**
59      * Configures a new quick tip instance and assigns it to a target element.  The following config values are
60      * supported (for example usage, see the {@link Ext.tip.QuickTipManager} class header):
61      * <div class="mdetail-params"><ul>
62      * <li>autoHide</li>
63      * <li>cls</li>
64      * <li>dismissDelay (overrides the singleton value)</li>
65      * <li>target (required)</li>
66      * <li>text (required)</li>
67      * <li>title</li>
68      * <li>width</li></ul></div>
69      * @param {Object} config The config object
70      */
71     register : function(config){
72         var configs = Ext.isArray(config) ? config : arguments,
73             i = 0,
74             len = configs.length,
75             target, j, targetLen;
76             
77         for (; i < len; i++) {
78             config = configs[i];
79             target = config.target;
80             if (target) {
81                 if (Ext.isArray(target)) {
82                     for (j = 0, targetLen = target.length; j < targetLen; j++) {
83                         this.targets[Ext.id(target[j])] = config;
84                     }
85                 } else{
86                     this.targets[Ext.id(target)] = config;
87                 }
88             }
89         }
90     },
91
92     /**
93      * Removes this quick tip from its element and destroys it.
94      * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
95      */
96     unregister : function(el){
97         delete this.targets[Ext.id(el)];
98     },
99     
100     /**
101      * Hides a visible tip or cancels an impending show for a particular element.
102      * @param {String/HTMLElement/Element} el The element that is the target of the tip.
103      */
104     cancelShow: function(el){
105         var me = this,
106             activeTarget = me.activeTarget;
107             
108         el = Ext.get(el).dom;
109         if (me.isVisible()) {
110             if (activeTarget && activeTarget.el == el) {
111                 me.hide();
112             }
113         } else if (activeTarget && activeTarget.el == el) {
114             me.clearTimer('show');
115         }
116     },
117     
118     getTipCfg: function(e) {
119         var t = e.getTarget(),
120             ttp, 
121             cfg;
122         
123         if(this.interceptTitles && t.title && Ext.isString(t.title)){
124             ttp = t.title;
125             t.qtip = ttp;
126             t.removeAttribute("title");
127             e.preventDefault();
128         } 
129         else {            
130             cfg = this.tagConfig;
131             t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
132             if (t) {
133                 ttp = t.getAttribute(cfg.namespace + cfg.attribute);
134             }
135         }
136         return ttp;
137     },
138
139     // private
140     onTargetOver : function(e){
141         var me = this,
142             target = e.getTarget(),
143             elTarget,
144             cfg,
145             ns,
146             ttp,
147             autoHide;
148         
149         if (me.disabled) {
150             return;
151         }
152
153         // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
154         // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
155         // that smashed Ext.EventObject.
156         me.targetXY = e.getXY();
157
158         if(!target || target.nodeType !== 1 || target == document || target == document.body){
159             return;
160         }
161         
162         if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
163             me.clearTimer('hide');
164             me.show();
165             return;
166         }
167         
168         if (target) {
169             Ext.Object.each(me.targets, function(key, value) {
170                 var targetEl = Ext.fly(value.target);
171                 if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
172                     elTarget = targetEl.dom;
173                     return false;
174                 }
175             });
176             if (elTarget) {
177                 me.activeTarget = me.targets[elTarget.id];
178                 me.activeTarget.el = target;
179                 me.anchor = me.activeTarget.anchor;
180                 if (me.anchor) {
181                     me.anchorTarget = target;
182                 }
183                 me.delayShow();
184                 return;
185             }
186         }
187
188         elTarget = Ext.get(target);
189         cfg = me.tagConfig;
190         ns = cfg.namespace; 
191         ttp = me.getTipCfg(e);
192         
193         if (ttp) {
194             autoHide = elTarget.getAttribute(ns + cfg.hide);
195                  
196             me.activeTarget = {
197                 el: target,
198                 text: ttp,
199                 width: +elTarget.getAttribute(ns + cfg.width) || null,
200                 autoHide: autoHide != "user" && autoHide !== 'false',
201                 title: elTarget.getAttribute(ns + cfg.title),
202                 cls: elTarget.getAttribute(ns + cfg.cls),
203                 align: elTarget.getAttribute(ns + cfg.align)
204                 
205             };
206             me.anchor = elTarget.getAttribute(ns + cfg.anchor);
207             if (me.anchor) {
208                 me.anchorTarget = target;
209             }
210             me.delayShow();
211         }
212     },
213
214     // private
215     onTargetOut : function(e){
216         var me = this;
217         
218         // If moving within the current target, and it does not have a new tip, ignore the mouseout
219         if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
220             return;
221         }
222
223         me.clearTimer('show');
224         if (me.autoHide !== false) {
225             me.delayHide();
226         }
227     },
228
229     // inherit docs
230     showAt : function(xy){
231         var me = this,
232             target = me.activeTarget;
233         
234         if (target) {
235             if (!me.rendered) {
236                 me.render(Ext.getBody());
237                 me.activeTarget = target;
238             }
239             if (target.title) {
240                 me.setTitle(target.title || '');
241                 me.header.show();
242             } else {
243                 me.header.hide();
244             }
245             me.body.update(target.text);
246             me.autoHide = target.autoHide;
247             me.dismissDelay = target.dismissDelay || me.dismissDelay;
248             if (me.lastCls) {
249                 me.el.removeCls(me.lastCls);
250                 delete me.lastCls;
251             }
252             if (target.cls) {
253                 me.el.addCls(target.cls);
254                 me.lastCls = target.cls;
255             }
256
257             me.setWidth(target.width);
258             
259             if (me.anchor) {
260                 me.constrainPosition = false;
261             } else if (target.align) { // TODO: this doesn't seem to work consistently
262                 xy = me.el.getAlignToXY(target.el, target.align);
263                 me.constrainPosition = false;
264             }else{
265                 me.constrainPosition = true;
266             }
267         }
268         me.callParent([xy]);
269     },
270
271     // inherit docs
272     hide: function(){
273         delete this.activeTarget;
274         this.callParent();
275     }
276 });
277