Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / core / src / dom / Element-more.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.Element
17  */
18
19 Ext.Element.addMethods((function(){
20     var focusRe = /button|input|textarea|select|object/;
21     return {
22         /**
23          * Monitors this Element for the mouse leaving. Calls the function after the specified delay only if
24          * the mouse was not moved back into the Element within the delay. If the mouse <i>was</i> moved
25          * back in, the function is not called.
26          * @param {Number} delay The delay <b>in milliseconds</b> to wait for possible mouse re-entry before calling the handler function.
27          * @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time.
28          * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to this Element.
29          * @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage:<pre><code>
30 // Hide the menu if the mouse moves out for 250ms or more
31 this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
32
33 ...
34 // Remove mouseleave monitor on menu destroy
35 this.menuEl.un(this.mouseLeaveMonitor);
36     </code></pre>
37          */
38         monitorMouseLeave: function(delay, handler, scope) {
39             var me = this,
40                 timer,
41                 listeners = {
42                     mouseleave: function(e) {
43                         timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
44                     },
45                     mouseenter: function() {
46                         clearTimeout(timer);
47                     },
48                     freezeEvent: true
49                 };
50
51             me.on(listeners);
52             return listeners;
53         },
54
55         /**
56          * Stops the specified event(s) from bubbling and optionally prevents the default action
57          * @param {String/String[]} eventName an event / array of events to stop from bubbling
58          * @param {Boolean} preventDefault (optional) true to prevent the default action too
59          * @return {Ext.Element} this
60          */
61         swallowEvent : function(eventName, preventDefault) {
62             var me = this;
63             function fn(e) {
64                 e.stopPropagation();
65                 if (preventDefault) {
66                     e.preventDefault();
67                 }
68             }
69
70             if (Ext.isArray(eventName)) {
71                 Ext.each(eventName, function(e) {
72                      me.on(e, fn);
73                 });
74                 return me;
75             }
76             me.on(eventName, fn);
77             return me;
78         },
79
80         /**
81          * Create an event handler on this element such that when the event fires and is handled by this element,
82          * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
83          * @param {String} eventName The type of event to relay
84          * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
85          * for firing the relayed event
86          */
87         relayEvent : function(eventName, observable) {
88             this.on(eventName, function(e) {
89                 observable.fireEvent(eventName, e);
90             });
91         },
92
93         /**
94          * Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
95          * @param {Boolean} forceReclean (optional) By default the element
96          * keeps track if it has been cleaned already so
97          * you can call this over and over. However, if you update the element and
98          * need to force a reclean, you can pass true.
99          */
100         clean : function(forceReclean) {
101             var me  = this,
102                 dom = me.dom,
103                 n   = dom.firstChild,
104                 nx,
105                 ni  = -1;
106     
107             if (Ext.Element.data(dom, 'isCleaned') && forceReclean !== true) {
108                 return me;
109             }
110
111             while (n) {
112                 nx = n.nextSibling;
113                 if (n.nodeType == 3) {
114                     // Remove empty/whitespace text nodes
115                     if (!(/\S/.test(n.nodeValue))) {
116                         dom.removeChild(n);
117                     // Combine adjacent text nodes
118                     } else if (nx && nx.nodeType == 3) {
119                         n.appendData(Ext.String.trim(nx.data));
120                         dom.removeChild(nx);
121                         nx = n.nextSibling;
122                         n.nodeIndex = ++ni;
123                     }
124                 } else {
125                     // Recursively clean
126                     Ext.fly(n).clean();
127                     n.nodeIndex = ++ni;
128                 }
129                 n = nx;
130             }
131
132             Ext.Element.data(dom, 'isCleaned', true);
133             return me;
134         },
135
136         /**
137          * Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#load} method. The method takes the same object
138          * parameter as {@link Ext.ElementLoader#load}
139          * @return {Ext.Element} this
140          */
141         load : function(options) {
142             this.getLoader().load(options);
143             return this;
144         },
145
146         /**
147         * Gets this element's {@link Ext.ElementLoader ElementLoader}
148         * @return {Ext.ElementLoader} The loader
149         */
150         getLoader : function() {
151             var dom = this.dom,
152                 data = Ext.Element.data,
153                 loader = data(dom, 'loader');
154     
155             if (!loader) {
156                 loader = Ext.create('Ext.ElementLoader', {
157                     target: this
158                 });
159                 data(dom, 'loader', loader);
160             }
161             return loader;
162         },
163
164         /**
165         * Update the innerHTML of this element, optionally searching for and processing scripts
166         * @param {String} html The new HTML
167         * @param {Boolean} [loadScripts=false] True to look for and process scripts
168         * @param {Function} [callback] For async script loading you can be notified when the update completes
169         * @return {Ext.Element} this
170          */
171         update : function(html, loadScripts, callback) {
172             var me = this,
173                 id,
174                 dom,
175                 interval;
176
177             if (!me.dom) {
178                 return me;
179             }
180             html = html || '';
181             dom = me.dom;
182
183             if (loadScripts !== true) {
184                 dom.innerHTML = html;
185                 Ext.callback(callback, me);
186                 return me;
187             }
188
189             id  = Ext.id();
190             html += '<span id="' + id + '"></span>';
191
192             interval = setInterval(function(){
193                 if (!document.getElementById(id)) {
194                     return false;
195                 }
196                 clearInterval(interval);
197                 var DOC    = document,
198                     hd     = DOC.getElementsByTagName("head")[0],
199                     re     = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
200                     srcRe  = /\ssrc=([\'\"])(.*?)\1/i,
201                     typeRe = /\stype=([\'\"])(.*?)\1/i,
202                     match,
203                     attrs,
204                     srcMatch,
205                     typeMatch,
206                     el,
207                     s;
208
209                 while ((match = re.exec(html))) {
210                     attrs = match[1];
211                     srcMatch = attrs ? attrs.match(srcRe) : false;
212                     if (srcMatch && srcMatch[2]) {
213                        s = DOC.createElement("script");
214                        s.src = srcMatch[2];
215                        typeMatch = attrs.match(typeRe);
216                        if (typeMatch && typeMatch[2]) {
217                            s.type = typeMatch[2];
218                        }
219                        hd.appendChild(s);
220                     } else if (match[2] && match[2].length > 0) {
221                         if (window.execScript) {
222                            window.execScript(match[2]);
223                         } else {
224                            window.eval(match[2]);
225                         }
226                     }
227                 }
228
229                 el = DOC.getElementById(id);
230                 if (el) {
231                     Ext.removeNode(el);
232                 }
233                 Ext.callback(callback, me);
234             }, 20);
235             dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, '');
236             return me;
237         },
238
239         // inherit docs, overridden so we can add removeAnchor
240         removeAllListeners : function() {
241             this.removeAnchor();
242             Ext.EventManager.removeAll(this.dom);
243             return this;
244         },
245     
246         /**
247          * Gets the parent node of the current element taking into account Ext.scopeResetCSS
248          * @protected
249          * @return {HTMLElement} The parent element
250          */
251         getScopeParent: function(){
252             var parent = this.dom.parentNode;
253             return Ext.scopeResetCSS ? parent.parentNode : parent;
254         },
255
256         /**
257          * Creates a proxy element of this element
258          * @param {String/Object} config The class name of the proxy element or a DomHelper config object
259          * @param {String/HTMLElement} [renderTo] The element or element id to render the proxy to (defaults to document.body)
260          * @param {Boolean} [matchBox=false] True to align and size the proxy to this element now.
261          * @return {Ext.Element} The new proxy element
262          */
263         createProxy : function(config, renderTo, matchBox) {
264             config = (typeof config == 'object') ? config : {tag : "div", cls: config};
265
266             var me = this,
267                 proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
268                                    Ext.DomHelper.insertBefore(me.dom, config, true);
269
270             proxy.setVisibilityMode(Ext.Element.DISPLAY);
271             proxy.hide();
272             if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
273                proxy.setBox(me.getBox());
274             }
275             return proxy;
276         },
277     
278         /**
279          * Checks whether this element can be focused.
280          * @return {Boolean} True if the element is focusable
281          */
282         focusable: function(){
283             var dom = this.dom,
284                 nodeName = dom.nodeName.toLowerCase(),
285                 canFocus = false,
286                 hasTabIndex = !isNaN(dom.tabIndex);
287             
288             if (!dom.disabled) {
289                 if (focusRe.test(nodeName)) {
290                     canFocus = true;
291                 } else {
292                     canFocus = nodeName == 'a' ? dom.href || hasTabIndex : hasTabIndex;
293                 }
294             }
295             return canFocus && this.isVisible(true);
296         }    
297     };
298 })());
299 Ext.Element.prototype.clearListeners = Ext.Element.prototype.removeAllListeners;
300