Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / src / adapter / prototype-bridge.js
1 /*!
2  * Ext JS Library 3.0.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 (function(){
8
9 var libFlyweight,
10     version = Prototype.Version.split('.'),
11     mouseEnterSupported = (parseInt(version[0]) >= 2) || (parseInt(version[1]) >= 7) || (parseInt(version[2]) >= 1),
12     mouseCache = {},
13     isXUL = Ext.isGecko ? function(node){ 
14         return Object.prototype.toString.call(node) == '[object XULElement]';
15     } : function(){},
16     isTextNode = Ext.isGecko ? function(node){
17         try{
18             return node.nodeType == 3;
19         }catch(e) {
20             return false;
21         }
22
23     } : function(node){
24         return node.nodeType == 3;
25     },
26     elContains = function(parent, child) {
27        if(parent && parent.firstChild){  
28          while(child) {
29             if(child === parent) {
30                 return true;
31             }
32             try {
33                 child = child.parentNode;
34             } catch(e) {
35                 // In FF if you mouseout an text input element
36                 // thats inside a div sometimes it randomly throws
37                 // Permission denied to get property HTMLDivElement.parentNode
38                 // See https://bugzilla.mozilla.org/show_bug.cgi?id=208427
39                 return false;
40             }                
41             if(child && (child.nodeType != 1)) {
42                 child = null;
43             }
44           }
45         }
46         return false;
47     },
48     checkRelatedTarget = function(e) {
49         var related = Ext.lib.Event.getRelatedTarget(e);
50         return !(isXUL(related) || elContains(e.currentTarget,related));
51     };
52
53 Ext.lib.Dom = {
54     getViewWidth : function(full){
55         return full ? this.getDocumentWidth() : this.getViewportWidth();
56     },
57
58     getViewHeight : function(full){
59         return full ? this.getDocumentHeight() : this.getViewportHeight();
60     },
61
62     getDocumentHeight: function() { // missing from prototype?
63         var scrollHeight = (document.compatMode != "CSS1Compat") ? document.body.scrollHeight : document.documentElement.scrollHeight;
64         return Math.max(scrollHeight, this.getViewportHeight());
65     },
66
67     getDocumentWidth: function() { // missing from prototype?
68         var scrollWidth = (document.compatMode != "CSS1Compat") ? document.body.scrollWidth : document.documentElement.scrollWidth;
69         return Math.max(scrollWidth, this.getViewportWidth());
70     },
71
72     getViewportHeight: function() { // missing from prototype?
73         var height = self.innerHeight;
74         var mode = document.compatMode;
75
76         if ( (mode || Ext.isIE) && !Ext.isOpera ) {
77             height = (mode == "CSS1Compat") ?
78                     document.documentElement.clientHeight : // Standards
79                     document.body.clientHeight; // Quirks
80         }
81
82         return height;
83     },
84
85     getViewportWidth: function() { // missing from prototype?
86         var width = self.innerWidth;  // Safari
87         var mode = document.compatMode;
88
89         if (mode || Ext.isIE) { // IE, Gecko, Opera
90             width = (mode == "CSS1Compat") ?
91                     document.documentElement.clientWidth : // Standards
92                     document.body.clientWidth; // Quirks
93         }
94         return width;
95     },
96
97     isAncestor : function(p, c){ // missing from prototype?
98         p = Ext.getDom(p);
99         c = Ext.getDom(c);
100         if (!p || !c) {return false;}
101
102         if(p.contains && !Ext.isSafari) {
103             return p.contains(c);
104         }else if(p.compareDocumentPosition) {
105             return !!(p.compareDocumentPosition(c) & 16);
106         }else{
107             var parent = c.parentNode;
108             while (parent) {
109                 if (parent == p) {
110                     return true;
111                 }
112                 else if (!parent.tagName || parent.tagName.toUpperCase() == "HTML") {
113                     return false;
114                 }
115                 parent = parent.parentNode;
116             }
117             return false;
118         }
119     },
120
121     getRegion : function(el){
122         return Ext.lib.Region.getRegion(el);
123     },
124
125     getY : function(el){
126         return this.getXY(el)[1];
127     },
128
129     getX : function(el){
130         return this.getXY(el)[0];
131     },
132
133     getXY : function(el){ // this initially used Position.cumulativeOffset but it is not accurate enough
134         var p, pe, b, scroll, bd = (document.body || document.documentElement);
135         el = Ext.getDom(el);
136
137         if(el == bd){
138             return [0, 0];
139         }
140
141         if (el.getBoundingClientRect) {
142             b = el.getBoundingClientRect();
143             scroll = fly(document).getScroll();
144             return [Math.round(b.left + scroll.left), Math.round(b.top + scroll.top)];
145         }
146         var x = 0, y = 0;
147
148         p = el;
149
150         var hasAbsolute = fly(el).getStyle("position") == "absolute";
151
152         while (p) {
153
154             x += p.offsetLeft;
155             y += p.offsetTop;
156
157             if (!hasAbsolute && fly(p).getStyle("position") == "absolute") {
158                 hasAbsolute = true;
159             }
160
161             if (Ext.isGecko) {
162                 pe = fly(p);
163
164                 var bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
165                 var bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;
166
167
168                 x += bl;
169                 y += bt;
170
171
172                 if (p != el && pe.getStyle('overflow') != 'visible') {
173                     x += bl;
174                     y += bt;
175                 }
176             }
177             p = p.offsetParent;
178         }
179
180         if (Ext.isSafari && hasAbsolute) {
181             x -= bd.offsetLeft;
182             y -= bd.offsetTop;
183         }
184
185         if (Ext.isGecko && !hasAbsolute) {
186             var dbd = fly(bd);
187             x += parseInt(dbd.getStyle("borderLeftWidth"), 10) || 0;
188             y += parseInt(dbd.getStyle("borderTopWidth"), 10) || 0;
189         }
190
191         p = el.parentNode;
192         while (p && p != bd) {
193             if (!Ext.isOpera || (p.tagName != 'TR' && fly(p).getStyle("display") != "inline")) {
194                 x -= p.scrollLeft;
195                 y -= p.scrollTop;
196             }
197             p = p.parentNode;
198         }
199         return [x, y];
200     },
201
202     setXY : function(el, xy){ // this initially used Position.cumulativeOffset but it is not accurate enough
203         el = Ext.fly(el, '_setXY');
204         el.position();
205         var pts = el.translatePoints(xy);
206         if(xy[0] !== false){
207             el.dom.style.left = pts.left + "px";
208         }
209         if(xy[1] !== false){
210             el.dom.style.top = pts.top + "px";
211         }
212     },
213
214     setX : function(el, x){
215         this.setXY(el, [x, false]);
216     },
217
218     setY : function(el, y){
219         this.setXY(el, [false, y]);
220     }
221 };
222
223 Ext.lib.Event = {
224     getPageX : function(e){
225         return Event.pointerX(e.browserEvent || e);
226     },
227
228     getPageY : function(e){
229         return Event.pointerY(e.browserEvent || e);
230     },
231
232     getXY : function(e){
233         e = e.browserEvent || e;
234         return [Event.pointerX(e), Event.pointerY(e)];
235     },
236
237     getTarget : function(e){
238         return Event.element(e.browserEvent || e);
239     },
240
241     resolveTextNode: function(node) {
242         return node && !isXUL(node) && isTextNode(node) ? node.parentNode : node;
243     },
244
245     getRelatedTarget: function(ev) { // missing from prototype?
246         ev = ev.browserEvent || ev;
247         var t = ev.relatedTarget;
248         if (!t) {
249             if (ev.type == "mouseout") {
250                 t = ev.toElement;
251             } else if (ev.type == "mouseover") {
252                 t = ev.fromElement;
253             }
254         }
255
256         return this.resolveTextNode(t);
257     },
258
259     on : function(el, eventName, fn){
260         if((eventName == 'mouseenter' || eventName == 'mouseleave') && !mouseEnterSupported){
261             var item = mouseCache[el.id] || (mouseCache[el.id] = {});
262             item[eventName] = fn;
263             fn = fn.createInterceptor(checkRelatedTarget);
264             eventName = (eventName == 'mouseenter') ? 'mouseover' : 'mouseout';
265         }
266         Event.observe(el, eventName, fn, false);
267     },
268
269     un : function(el, eventName, fn){
270         if((eventName == 'mouseenter' || eventName == 'mouseleave') && !mouseEnterSupported){
271             var item = mouseCache[el.id], 
272                 ev = item && item[eventName];
273
274             if(ev){
275                 fn = ev.fn;
276                 delete item[eventName];
277                 eventName = (eventName == 'mouseenter') ? 'mouseover' : 'mouseout';
278             }
279         }
280         Event.stopObserving(el, eventName, fn, false);
281     },
282
283     purgeElement : function(el){
284         // no equiv?
285     },
286
287     preventDefault : function(e){   // missing from prototype?
288         e = e.browserEvent || e;
289         if(e.preventDefault) {
290             e.preventDefault();
291         } else {
292             e.returnValue = false;
293         }
294     },
295
296     stopPropagation : function(e){   // missing from prototype?
297         e = e.browserEvent || e;
298         if(e.stopPropagation) {
299             e.stopPropagation();
300         } else {
301             e.cancelBubble = true;
302         }
303     },
304
305     stopEvent : function(e){
306         Event.stop(e.browserEvent || e);
307     },
308
309     onAvailable : function(id, fn, scope){  // no equiv
310         var start = new Date(), iid;
311         var f = function(){
312             if(start.getElapsed() > 10000){
313                 clearInterval(iid);
314             }
315             var el = document.getElementById(id);
316             if(el){
317                 clearInterval(iid);
318                 fn.call(scope||window, el);
319             }
320         };
321         iid = setInterval(f, 50);
322     }
323 };
324
325 Ext.lib.Ajax = function(){
326     var createSuccess = function(cb){
327          return cb.success ? function(xhr){
328             cb.success.call(cb.scope||window, {
329                 responseText: xhr.responseText,
330                 responseXML : xhr.responseXML,
331                 argument: cb.argument
332             });
333          } : Ext.emptyFn;
334     };
335     var createFailure = function(cb){
336          return cb.failure ? function(xhr){
337             cb.failure.call(cb.scope||window, {
338                 responseText: xhr.responseText,
339                 responseXML : xhr.responseXML,
340                 argument: cb.argument
341             });
342          } : Ext.emptyFn;
343     };
344     return {
345         request : function(method, uri, cb, data, options){
346             var o = {
347                 method: method,
348                 parameters: data || '',
349                 timeout: cb.timeout,
350                 onSuccess: createSuccess(cb),
351                 onFailure: createFailure(cb)
352             };
353             if(options){
354                 var hs = options.headers;
355                 if(hs){
356                     o.requestHeaders = hs;
357                 }
358                 if(options.xmlData){
359                     method = (method ? method : (options.method ? options.method : 'POST'));
360                     if (!hs || !hs['Content-Type']){
361                         o.contentType = 'text/xml';
362                     }
363                     o.postBody = options.xmlData;
364                     delete o.parameters;
365                 }
366                 if(options.jsonData){
367                     method = (method ? method : (options.method ? options.method : 'POST'));
368                     if (!hs || !hs['Content-Type']){
369                         o.contentType = 'application/json';
370                     }
371                     o.postBody = typeof options.jsonData == 'object' ? Ext.encode(options.jsonData) : options.jsonData;
372                     delete o.parameters;
373                 }
374             }
375             new Ajax.Request(uri, o);
376         },
377
378         formRequest : function(form, uri, cb, data, isUpload, sslUri){
379             new Ajax.Request(uri, {
380                 method: Ext.getDom(form).method ||'POST',
381                 parameters: Form.serialize(form)+(data?'&'+data:''),
382                 timeout: cb.timeout,
383                 onSuccess: createSuccess(cb),
384                 onFailure: createFailure(cb)
385             });
386         },
387
388         isCallInProgress : function(trans){
389             return false;
390         },
391
392         abort : function(trans){
393             return false;
394         },
395         
396         serializeForm : function(form){
397             return Form.serialize(form.dom||form);
398         }
399     };
400 }();
401
402
403 Ext.lib.Anim = function(){
404     
405     var easings = {
406         easeOut: function(pos) {
407             return 1-Math.pow(1-pos,2);
408         },
409         easeIn: function(pos) {
410             return 1-Math.pow(1-pos,2);
411         }
412     };
413     var createAnim = function(cb, scope){
414         return {
415             stop : function(skipToLast){
416                 this.effect.cancel();
417             },
418
419             isAnimated : function(){
420                 return this.effect.state == 'running';
421             },
422
423             proxyCallback : function(){
424                 Ext.callback(cb, scope);
425             }
426         };
427     };
428     return {
429         scroll : function(el, args, duration, easing, cb, scope){
430             // not supported so scroll immediately?
431             var anim = createAnim(cb, scope);
432             el = Ext.getDom(el);
433             if(typeof args.scroll.to[0] == 'number'){
434                 el.scrollLeft = args.scroll.to[0];
435             }
436             if(typeof args.scroll.to[1] == 'number'){
437                 el.scrollTop = args.scroll.to[1];
438             }
439             anim.proxyCallback();
440             return anim;
441         },
442
443         motion : function(el, args, duration, easing, cb, scope){
444             return this.run(el, args, duration, easing, cb, scope);
445         },
446
447         color : function(el, args, duration, easing, cb, scope){
448             return this.run(el, args, duration, easing, cb, scope);
449         },
450
451         run : function(el, args, duration, easing, cb, scope, type){
452             var o = {};
453             for(var k in args){
454                 switch(k){   // scriptaculous doesn't support, so convert these
455                     case 'points':
456                         var by, pts, e = Ext.fly(el, '_animrun');
457                         e.position();
458                         if(by = args.points.by){
459                             var xy = e.getXY();
460                             pts = e.translatePoints([xy[0]+by[0], xy[1]+by[1]]);
461                         }else{
462                             pts = e.translatePoints(args.points.to);
463                         }
464                         o.left = pts.left+'px';
465                         o.top = pts.top+'px';
466                     break;
467                     case 'width':
468                         o.width = args.width.to+'px';
469                     break;
470                     case 'height':
471                         o.height = args.height.to+'px';
472                     break;
473                     case 'opacity':
474                         o.opacity = String(args.opacity.to);
475                     break;
476                     default:
477                         o[k] = String(args[k].to);
478                     break;
479                 }
480             }
481             var anim = createAnim(cb, scope);
482             anim.effect = new Effect.Morph(Ext.id(el), {
483                 duration: duration,
484                 afterFinish: anim.proxyCallback,
485                 transition: easings[easing] || Effect.Transitions.linear,
486                 style: o
487             });
488             return anim;
489         }
490     };
491 }();
492
493
494 // all lib flyweight calls use their own flyweight to prevent collisions with developer flyweights
495 function fly(el){
496     if(!libFlyweight){
497         libFlyweight = new Ext.Element.Flyweight();
498     }
499     libFlyweight.dom = el;
500     return libFlyweight;
501 }
502     
503 Ext.lib.Region = function(t, r, b, l) {
504     this.top = t;
505     this[1] = t;
506     this.right = r;
507     this.bottom = b;
508     this.left = l;
509     this[0] = l;
510 };
511
512 Ext.lib.Region.prototype = {
513     contains : function(region) {
514         return ( region.left   >= this.left   &&
515                  region.right  <= this.right  &&
516                  region.top    >= this.top    &&
517                  region.bottom <= this.bottom    );
518
519     },
520
521     getArea : function() {
522         return ( (this.bottom - this.top) * (this.right - this.left) );
523     },
524
525     intersect : function(region) {
526         var t = Math.max( this.top,    region.top    );
527         var r = Math.min( this.right,  region.right  );
528         var b = Math.min( this.bottom, region.bottom );
529         var l = Math.max( this.left,   region.left   );
530
531         if (b >= t && r >= l) {
532             return new Ext.lib.Region(t, r, b, l);
533         } else {
534             return null;
535         }
536     },
537     union : function(region) {
538         var t = Math.min( this.top,    region.top    );
539         var r = Math.max( this.right,  region.right  );
540         var b = Math.max( this.bottom, region.bottom );
541         var l = Math.min( this.left,   region.left   );
542
543         return new Ext.lib.Region(t, r, b, l);
544     },
545
546     constrainTo : function(r) {
547             this.top = this.top.constrain(r.top, r.bottom);
548             this.bottom = this.bottom.constrain(r.top, r.bottom);
549             this.left = this.left.constrain(r.left, r.right);
550             this.right = this.right.constrain(r.left, r.right);
551             return this;
552     },
553
554     adjust : function(t, l, b, r){
555         this.top += t;
556         this.left += l;
557         this.right += r;
558         this.bottom += b;
559         return this;
560     }
561 };
562
563 Ext.lib.Region.getRegion = function(el) {
564     var p = Ext.lib.Dom.getXY(el);
565
566     var t = p[1];
567     var r = p[0] + el.offsetWidth;
568     var b = p[1] + el.offsetHeight;
569     var l = p[0];
570
571     return new Ext.lib.Region(t, r, b, l);
572 };
573
574 Ext.lib.Point = function(x, y) {
575    if (Ext.isArray(x)) {
576       y = x[1];
577       x = x[0];
578    }
579     this.x = this.right = this.left = this[0] = x;
580     this.y = this.top = this.bottom = this[1] = y;
581 };
582
583 Ext.lib.Point.prototype = new Ext.lib.Region();
584
585
586 // prevent IE leaks
587 if(Ext.isIE) {
588     function fnCleanUp() {
589         var p = Function.prototype;
590         delete p.createSequence;
591         delete p.defer;
592         delete p.createDelegate;
593         delete p.createCallback;
594         delete p.createInterceptor;
595
596         window.detachEvent("onunload", fnCleanUp);
597     }
598     window.attachEvent("onunload", fnCleanUp);
599 }
600 })();