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