More bug fixes and added documentation - passes the test suite now.
[jquery.git] / event / event.js
index ebb7897..0c7b0b1 100644 (file)
-var e = ["blur","focus","contextmenu","load","resize","scroll","unload",
-       "click","dblclick","mousedown","mouseup","mouseenter","mouseleave",
-       "mousemove","mouseover","mouseout","change","reset","select","submit",
-       "keydown","keypress","keyup","abort","error","ready"];
+jQuery.fn.extend({
+
+       // We're overriding the old toggle function, so
+       // remember it for later
+       _toggle: jQuery.fn.toggle,
        
-for ( var i = 0; i < e.length; i++ ) {
-       (function(){
-               var o = e[i];
-               $.fn[o] = function(f){ return this.bind(o, f); };
-               $.fn["un"+o] = function(f){ return this.unbind(o, f); };
-               $.fn["do"+o] = function(){ return this.trigger(o); };
-               $.fn["one"+o] = function(f){ return this.bind(o, function(e){
-                       if ( this[o+f] != null ) return true;
-                       this[o+f]++;
-                       return $.apply(this,f,[e]);
-               }); };
+       /**
+        * Toggle between two function calls every other click.
+        */
+       toggle: function(a,b) {
+               // If two functions are passed in, we're
+               // toggling on a click
+               return a && b ? this.click(function(e){
+                       // Figure out which function to execute
+                       this.last = this.last == a ? b : a;
+                       
+                       // Make sure that clicks stop
+                       e.preventDefault();
+                       
+                       // and execute the function
+                       return this.last.apply( this, [e] ) || false;
+               }) :
                
-               // Deprecated
-               //$.fn["on"+o] = function(f){ return this.bind(o, f); };
-       })();
-}
-
-$.fn.hover = function(f,g) {
-       // Check if mouse(over|out) are still within the same parent element
-       return this.each(function(){
-               var obj = this;
-               addEvent(this, "mouseover", function(e) {
-                       var p = ( e.fromElement != null ? e.fromElement : e.relatedTarget );
-                       while ( p && p != obj ) p = p.parentNode;
-                       if ( p == obj ) return false;
-                       return $.apply(obj,f,[e]);
-               });
-               addEvent(this, "mouseout", function(e) {
-                       var p = ( e.toElement != null ? e.toElement : e.relatedTarget );
-                       while ( p && p != obj ) p = p.parentNode;
-                       if ( p == obj ) return false;
-                       return $.apply(obj,g,[e]);
-               });
-       });
-};
-
-// Deprecated
-$.fn.onhover = $.fn.hover;
+               // Otherwise, execute the old toggle function
+               this._toggle();
+       },
+       
+       /**
+        * Toggle between two function calls on mouse over/out.
+        */
+       hover: function(f,g) {
+               
+               // A private function for haandling mouse 'hovering'
+               function handleHover(e) {
+                       // Check if mouse(over|out) are still within the same parent element
+                       var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
+       
+                       // Traverse up the tree
+                       while ( p && p != this ) p = p.parentNode;
+                       
+                       // If we actually just moused on to a sub-element, ignore it
+                       if ( p == this ) return false;
+                       
+                       // Execute the right function
+                       return (e.type == "mouseover" ? f : g).apply(this, [e]);
+               }
+               
+               // Bind the function to the two event listeners
+               return this.mouseover(handleHover).mouseout(handleHover);
+       },
+       
+       /**
+        * Bind a function to fire when the DOM is ready.
+        */
+       ready: function(f) {
+               // If the DOM is already ready
+               if ( jQuery.isReady )
+                       // Execute the function immediately
+                       f.apply( document );
+                       
+               // Otherwise, remember the function for later
+               else {
+                       // Add the function to the wait list
+                       jQuery.readyList.push( f );
+               }
+       
+               return this;
+       }
+});
 
-$.fn.ready = function(f) {
-       return this.each(function(){
-               if ( this.$$timer ) {
-                       this.$$ready.push( f );
-               } else {
-                       var obj = this;
-                       this.$$ready = [ f ];
-                       this.$$timer = setInterval( function(){
-                               if ( obj && obj.getElementsByTagName && obj.getElementById && obj.body ) {
-                                       clearInterval( obj.$$timer );
-                                       obj.$$timer = null;
-                                       for ( var i = 0; i < obj.$$ready.length; i++ )
-                                               $.apply( obj, obj.$$ready[i] );
-                                       obj.$$ready = null;
-                               }
-                       }, 13 );
+jQuery.extend({
+       /*
+        * All the code that makes DOM Ready work nicely.
+        */
+       isReady: false,
+       readyList: [],
+       
+       // Handle when the DOM is ready
+       ready: function() {
+               // Make sure that the DOM is not already loaded
+               if ( !jQuery.isReady ) {
+                       // Remember that the DOM is ready
+                       jQuery.isReady = true;
+                       
+                       // If there are functions bound, to execute
+                       if ( jQuery.readyList ) {
+                               // Execute all of them
+                               for ( var i = 0; i < jQuery.readyList.length; i++ )
+                                       jQuery.readyList[i].apply( document );
+                               
+                               // Reset the list of functions
+                               jQuery.readyList = null;
+                       }
                }
-       });
-};
+       }
+});
 
-// Deprecated
-$.fn.onready = $.fn.ready;
+new function(){
+       /*
+        * Bind a number of event-handling functions, dynamically
+        */
+       var e = ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+               "mousedown,mouseup,mousemove,mouseover,mouseout,change,reset,select," + 
+               "submit,keydown,keypress,keyup,error").split(",");
 
-$.fn.toggle = function(a,b) {
-       return a && b ? this.click(function(e){
-               this.$$last = this.$$last == a ? b : a;
-               e.preventDefault();
-               return $.apply( this, this.$$last, [e] ) || false;
-       }) : this._toggle();
-};
+       // Go through all the event names, but make sure that
+       // it is enclosed properly
+       for ( var i = 0; i < e.length; i++ ) new function(){
+                       
+               var o = e[i];
+               
+               // Handle event binding
+               jQuery.fn[o] = function(f){
+                       return f ? this.bind(o, f) : this.trigger(o);
+               };
+               
+               // Handle event unbinding
+               jQuery.fn["un"+o] = function(f){ return this.unbind(o, f); };
+               
+               // Finally, handle events that only fire once
+               jQuery.fn["one"+o] = function(f){
+                       // Attach the event listener
+                       return this.bind(o, function(e){
+                               // TODO: Remove the event listener, instead of this hack
+                               
+                               // If this function has already been executed, stop
+                               if ( this[o+f] !== null ) return;
+                               
+                               // Otherwise, mark as having been executed
+                               this[o+f]++;
+                               
+                               // And execute the bound function
+                               return f.apply(this, [e]);
+                       });
+               };
+                       
+       }
+       
+       // If Mozilla is used
+       if ( jQuery.browser.mozilla || jQuery.browser.opera ) {
+               // Use the handy event callback
+               document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+       
+       // If IE is used, use the excellent hack by Matthias Miller
+       // http://www.outofhanwell.com/blog/index.php?title=the_window_onload_problem_revisited
+       } else if ( jQuery.browser.msie ) {
+       
+               // Only works if you document.write() it
+               document.write("<scr" + "ipt id=__ie_init defer=true " + 
+                       "src=https:///><\/script>");
+       
+               // Use the defer script hack
+               var script = document.getElementById("__ie_init");
+               script.onreadystatechange = function() {
+                       if ( this.readyState == "complete" )
+                               jQuery.ready();
+               };
+       
+               // Clear from memory
+               script = null;
+       
+       // If Safari  is used
+       } else if ( jQuery.browser.safari ) {
+               // Continually check to see if the document.readyState is valid
+               jQuery.safariTimer = setInterval(function(){
+                       // loaded and complete are both valid states
+                       if ( document.readyState == "loaded" || 
+                               document.readyState == "complete" ) {
+       
+                               // If either one are found, remove the timer
+                               clearInterval( jQuery.safariTimer );
+                               jQuery.safariTimer = null;
+       
+                               // and execute any waiting functions
+                               jQuery.ready();
+                       }
+               }, 10);
+       }
+       
+       // A fallback to window.onload, that will always work
+       jQuery.event.add( window, "load", jQuery.ready );
+       
+}