Guest User

jQuery 1.7.2 Live Event Ordering by priority

a guest
Apr 4th, 2012
2,144
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Copyright (c) 2012 Matthias Melitzer
  3.  */
  4. (function (window, $j, undefined) {
  5.    
  6.     var statics = {
  7.         eventNamespace: 'priorityHandler',
  8.         priorityData: 'POI.priority',
  9.         handlerNS: 'handlerQueue',
  10.         capturedEventNames: {},
  11.         originalDispatchMethod: undefined,
  12.         max_priority: 10,
  13.         min_priority: -10,
  14.         peek: 'justPeeking'
  15.     };
  16.    
  17.     $j.widget("POI.onPriority", {
  18.         // These options will be used as defaults
  19.         options: {
  20.             events: undefined,
  21.             selector: undefined,
  22.             data: undefined,
  23.             fn: undefined,
  24.             priority: 0,
  25.             max_priority: statics.max_priority,
  26.             min_priority: statics.min_priority
  27.         },
  28.        
  29.         //runs only once/DOM element
  30.         _create: function() {
  31.             //capture for other events which don't need ordering
  32.             if (!statics.originalDispatchMethod) {
  33.                 statics.originalDispatchMethod = $j.event.dispatch;
  34.                 this._replaceDispatchMethod();
  35.             }
  36.         },
  37.        
  38.         //runs each time the plugin is called which might be mutliple times/DOM element
  39.         //Event-map isn't supported yet
  40.         _init: function () {
  41.             var options = this.options,
  42.                 eventNames, i;
  43.            
  44.             eventNames = this._parseEventNames(options.events, options.selector);
  45.             for(i = 0; i<eventNames.length;i++){
  46.                 var name = eventNames[i];
  47.                
  48.                 //only bind if not already bound to make sure the event bubbles all the way
  49.                 if (!statics.capturedEventNames[name]) {
  50.                     statics.capturedEventNames[name] = {};
  51.                     $j(window).on(name + "." + statics.eventNamespace, function(){
  52.                         //no action necessary, only to ensure that event bubbling is over
  53.                     });
  54.                 }
  55.             };
  56.            
  57.             if(options.priority > statics.max_priority || options.priority < statics.min_priority)
  58.             {
  59.                 throw "The priority can only be set between "+statics.max_priority+" and "+statics.min_priority+".";
  60.             }
  61.             options.data = (options.data || {});
  62.             options.data[statics.priorityData] = options.priority;
  63.                        
  64.             this.element.on(options.events, options.selector, options.data, options.fn);
  65.         },
  66.        
  67.         /*********************************************************************************
  68.          *
  69.          *                          jQuery private methods
  70.          *
  71.          *********************************************************************************/
  72.         _hoverHack: function( events ) {
  73.             var rhoverHack = /(?:^|\s)hover(\.\S+)?\b/;
  74.             return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
  75.         },
  76.        
  77.         /*********************************************************************************
  78.          *
  79.          *                          jQuery private methods
  80.          *
  81.          *********************************************************************************/
  82.  
  83.         //partially taken from jQuery
  84.         _parseEventNames: function(types, selector){
  85.             var rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
  86.                 t, tns, type, result = []; 
  87.            
  88.             // Handle multiple events separated by a space
  89.             // jQuery(...).bind("mouseover mouseout", fn);
  90.             types = jQuery.trim(this._hoverHack(types)).split(" ");
  91.             for (t = 0; t < types.length; t++) {
  92.                 tns = rtypenamespace.exec(types[t]) || [];
  93.                 type = tns[1];
  94.                 // If event changes its type, use the special event handlers for the changed type
  95.                 special = jQuery.event.special[type] || {};
  96.                 // If selector defined, determine special event api type, otherwise given type
  97.                 type = (selector ? special.delegateType : special.bindType) || type;
  98.                 result[t] = type;
  99.             }
  100.             return result;
  101.         },
  102.  
  103.         /**
  104.          * Method has to be overwritten as we want to encapsulate the event
  105.          * handling chain, first the priority of events is taken into account,
  106.          * secondly the regular bubbling hierarchy is used. Most of the code is
  107.          * copied directly from jQuery, the only adaptions are regarding resorting
  108.          * the handler's based on a priority and the wrapping of the event bubbling
  109.          * to ensure all handler's are included.
  110.          **/
  111.         _replaceDispatchMethod: function() {
  112.             var _quickIs = function( elem, m ) {
  113.                 var attrs = elem.attributes || {};
  114.                 return ((!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
  115.                         (!m[2] || (attrs.id || {}).value === m[2]) &&
  116.                         (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
  117.                 );
  118.             };
  119.            
  120.             //CAREFUL: this isn't ourself here, rather it's the element
  121.             $j.event.dispatch = function(event){
  122.                
  123.                 //event isn't interested in ordering by priority
  124.                 if (statics.capturedEventNames[event.type] === undefined) {
  125.                     return statics.originalDispatchMethod ? statics.originalDispatchMethod.apply(this, arguments) : null;
  126.                 }
  127.                
  128.                 // Make a writable $j.Event from the native event object
  129.                 event = $j.event.fix(event || window.event);
  130.                 var handlers = (($j._data(this, "events") || {})[event.type] || []),
  131.                     delegateCount = handlers.delegateCount,
  132.                     args = [].slice.call(arguments, 0),
  133.                     run_all = !event.exclusive && !event.namespace,
  134.                     special = $j.event.special[event.type] || {},
  135.                     handlerQueue = [], i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
  136.                    
  137.                 // Use the fix-ed $j.Event rather than the (read-only) native event
  138.                 args[0] = event;
  139.                 event.delegateTarget = this;
  140.                 // Call the preDispatch hook for the mapped type, and let it bail if desired
  141.                 if (special.preDispatch && special.preDispatch.call(this, event) === false) {
  142.                     return;
  143.                 }
  144.                 // Determine handlers that should run if there are delegated events
  145.                 // Avoid non-left-click bubbling in Firefox (#3861)
  146.                 if (delegateCount && !(event.button && event.type === "click")) {
  147.                     // Pregenerate a single $j object for reuse with .is()
  148.                     jqcur = $j(this);
  149.                     jqcur.context = this.ownerDocument || this;
  150.                     for (cur = event.target; cur != this; cur = cur.parentNode || this) {
  151.                         // Don't process events on disabled elements (#6911, #8165)
  152.                         if (cur.disabled !== true) {
  153.                             selMatch = {};
  154.                             matches = [];
  155.                             jqcur[0] = cur;
  156.                             for (i = 0; i < delegateCount; i++) {
  157.                                 handleObj = handlers[i];
  158.                                 sel = handleObj.selector;
  159.                                 if (selMatch[sel] === undefined) {
  160.                                     selMatch[sel] = (handleObj.quick ? _quickIs(cur, handleObj.quick) : jqcur.is(sel));
  161.                                 }
  162.                                 if (selMatch[sel]) {
  163.                                     matches.push(handleObj);
  164.                                 }
  165.                             }
  166.                             if (matches.length) {
  167.                                 handlerQueue.push({
  168.                                     elem: cur,
  169.                                     matches: matches
  170.                                 });
  171.                             }
  172.                         }
  173.                     }
  174.                 }
  175.                 // Add the remaining (directly-bound) handlers
  176.                 if (handlers.length > delegateCount) {
  177.                     handlerQueue.push({
  178.                         elem: this,
  179.                         matches: handlers.slice(delegateCount)
  180.                     });
  181.                 }
  182.                                
  183.                 //has to exist as we have an exit clause on top of the function
  184.                 statics.capturedEventNames[event.type][statics.handlerNS] = statics.capturedEventNames[event.type][statics.handlerNS] || [];
  185.                
  186.                 if(!!handlerQueue.length) {
  187.                     statics.capturedEventNames[event.type][statics.handlerNS].push(handlerQueue);
  188.                 }
  189.                                
  190.                 if(this === window)
  191.                 {
  192.                     var accumulatedHandlers = _.flatten(statics.capturedEventNames[event.type][statics.handlerNS]),
  193.                         sortedHandlers = [], sortedElement, sortedMatch, sortedMatches, requireResort = false;
  194.                    
  195.                     //from now on we work with the sorted copy or if it wasn't necessary just with a local copy
  196.                     delete statics.capturedEventNames[event.type][statics.handlerNS];                    
  197.                    
  198.                     //check if our handler is the only one registered, if so, ignore
  199.                     if (accumulatedHandlers.length === 1                        //only one level is interested in the event
  200.                         && accumulatedHandlers[0].matches.length === 1          //only one handler (that's us)
  201.                         && accumulatedHandlers[0].matches[0].namespace === statics.eventNamespace)  //make sure it's our handler
  202.                     {
  203.                         //no sorting or handling necessary
  204.                         return;
  205.                     }
  206.                    
  207.                     //iterate over all levels
  208.                     for (i = 0; i < accumulatedHandlers.length; i++) {
  209.                         sortedElement = accumulatedHandlers[i].elem;
  210.                         sortedMatches = accumulatedHandlers[i].matches;
  211.                         for (j = 0; j < sortedMatches.length; j++) {
  212.                             sortedMatch = sortedMatches[j];
  213.                             if(sortedMatch.data && sortedMatch.data[statics.priorityData]){
  214.                                 requireResort = true;
  215.                             }
  216.                             sortedHandlers.push({
  217.                                 elem: sortedElement,
  218.                                 matches: sortedMatch
  219.                             });
  220.                         }
  221.                     }
  222.                    
  223.                     if(!!sortedHandlers.length && requireResort){
  224.                         function sortByPriority(a,b){  
  225.                             var aPrio = (a.matches.data || {})[statics.priorityData] || 0,
  226.                                 bPrio = (b.matches.data || {})[statics.priorityData] || 0;
  227.                             //revert order if prio is not 0, keep prio for 0
  228.                             if (aPrio || bPrio) {
  229.                                 return aPrio > bPrio ? -1 : 1;
  230.                             }
  231.                             return aPrio >= bPrio ? -1 : 1;
  232.                         };
  233.                         sortedHandlers.sort(sortByPriority);
  234.                     }
  235.                    
  236.                     //used when peeking to get the highest priority
  237.                     if(sortedHandlers[0].matches.namespace.indexOf(statics.peek)!==-1)
  238.                     {
  239.                         var matched = sortedHandlers[0];
  240.                         args.push(sortedHandlers.slice(1));
  241.                         return matched.matches.handler.apply(matched.elem, args);
  242.                     }
  243.                    
  244.                     // Run delegates first; they may want to stop propagation beneath us
  245.                     for (i = 0; i < sortedHandlers.length; i++) {
  246.                         matched = sortedHandlers[i];
  247.                         //uncommented as bubbling doesn't really match our concept of priorities
  248.                         //stop bubbling only if the context switches
  249. //                      if(event.isPropagationStopped() && lastContext && lastContext != matched.elem){
  250. //                          //we don't allow preventing bubbling when specifying a priority
  251. //                          currentPriority = (event.data || {})[statics.priorityData]||0;
  252. //                         
  253. //                          if (!currentPriority) {
  254. //                              return;
  255. //                          }
  256. //                      }
  257. //                      lastContext = matched.elem;
  258.                         event.currentTarget = matched.elem;
  259.                         if(matched.matches){
  260.                             if (event.isImmediatePropagationStopped()) {
  261.                                 return;
  262.                             }
  263.                             handleObj = matched.matches;
  264.                             // Triggered event must either 1) be non-exclusive and have no namespace, or
  265.                             // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
  266.                             if (run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test(handleObj.namespace)) {
  267.                                 event.data = handleObj.data;
  268.                                 event.handleObj = handleObj;
  269.                                 ret = (($j.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args);
  270.                                 if (ret !== undefined) {
  271.                                     event.result = ret;
  272.                                     if (ret === false) {
  273.                                         event.preventDefault();
  274.                                         event.stopPropagation();
  275.                                     }
  276.                                 }
  277.                             }
  278.                         }
  279.                     }
  280.                     // Call the postDispatch hook for the mapped type
  281.                     if (special.postDispatch) {
  282.                         special.postDispatch.call(this, event);
  283.                     }
  284.                     return event.result;
  285.                 }
  286.                 // Call the postDispatch hook for the mapped type
  287.                 if (special.postDispatch) {
  288.                     special.postDispatch.call(this, event);
  289.                 }
  290.             }          
  291.         },
  292.        
  293.         _setOption: function (key, value) {
  294.             switch (key) {
  295.                 case "max_priority":
  296.                     if(value<this.options.min_priority){
  297.                         throw "The max. priority can't be lower than the min. priority";
  298.                     }
  299.                     statics.max_priority = value;
  300.                     break;
  301.                 case "min_priority":
  302.                     if(value>this.options.max_priority){
  303.                         throw "The min. priority can't be higher than the max. priority";
  304.                     }
  305.                     statics.min_priority = value;
  306.                     break;
  307.             }
  308.             // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget
  309.             $j.Widget.prototype._setOption.apply(this, arguments);
  310.             // In jQuery UI 1.9 and above, you use the _super method instead
  311.             // this._super("_setOption", key, value);
  312.         },
  313.  
  314.         // Use the destroy method to clean up any modifications your widget has made to the DOM
  315.         destroy: function () {
  316.             // In jQuery UI 1.8, you must invoke the destroy method from the base widget
  317.             $.Widget.prototype.destroy.call(this);
  318.             // In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
  319.         }
  320.     });
  321.    
  322.     $j.POI = $j.POI || {};
  323.    
  324.     $j.extend($j.POI, {
  325.         peek: function(eventName, selector, lowest){
  326.             if (typeof eventName !== 'string') {
  327.                 return;
  328.             }
  329.             var element, returnQueue = [];
  330.             if (typeof selector === 'string' || !(selector instanceof $j)) {
  331.                 element = $j(selector);
  332.             }
  333.             element = element || selector;
  334.             if (!element || (element && element.length === 0)) {
  335.                 throw "No elements were found with the provided selector";
  336.             }
  337.             $j(window).onPrio(eventName + "." + statics.peek, function(event, data){
  338.                 $j(window).off("." + statics.peek);
  339.                 returnQueue = data || returnQueue;
  340.             }, statics.max_priority);
  341.             element.trigger(eventName + "." + statics.peek);
  342.             if (lowest) {
  343.                 return ((((returnQueue || [])[returnQueue.length - 1] || {}).matches || {}).data || {})['POI.priority'] || 0;
  344.             }
  345.             return ((((returnQueue || [])[0] || {}).matches || {}).data || {})['POI.priority'] || 0;
  346.         }
  347.     });
  348.  
  349.     function registerHandler(types, selector, data, fn, firstOrLast){
  350.         //taken from jQuery.fn.on
  351.         var origFn, type;
  352.         // Types can be a map of types/handlers
  353.         if (typeof types === "object") {
  354.             // ( types-Object, selector, data )
  355.             if (typeof selector !== "string") { // && selector != null
  356.                 // ( types-Object, data )
  357.                 data = data || selector;
  358.                 selector = undefined;
  359.             }
  360.             for (type in types) {
  361.                 registerHandler.call(this, type, selector, data, types[type], firstOrLast);
  362.             }
  363.             return this;
  364.         }
  365.         if (data == null && fn == null) {
  366.             // ( types, fn )
  367.             fn = selector;
  368.             data = selector = undefined;
  369.         } else if (fn == null) {
  370.             if (typeof selector === "string") {
  371.                 // ( types, selector, fn )
  372.                 fn = data;
  373.                 data = undefined;
  374.             } else {
  375.                 // ( types, data, fn )
  376.                 fn = data;
  377.                 data = selector;
  378.                 selector = undefined;
  379.             }
  380.         }
  381.         if (fn === false) {
  382.             fn = returnFalse;
  383.         } else if (!fn) {
  384.             return this;
  385.         }
  386.         return this.each(function(){
  387.             if(typeof firstOrLast === 'boolean'){
  388.                 var sel = selector || this;
  389.                 priority = firstOrLast ? $j.POI.peek(types, sel, firstOrLast)-1 : $j.POI.peek(types, sel, firstOrLast)+1;
  390.                 if (priority > statics.max_priority) {
  391.                     statics.max_priority = priority+5;
  392.                 }
  393.                 else if (priority < statics.min_priority) {
  394.                     statics.min_priority = priority-5;
  395.                 }
  396.             }
  397.             $j(this).onPriority({'events':types, 'selector':selector, 'data':data, 'fn':fn, 'priority':priority});
  398.         });
  399.     }
  400.  
  401.     $j.fn.addFirst = function(types, selector, data, fn){
  402.         return registerHandler.call(this, types, selector, data, fn, false);
  403.     }
  404.    
  405.     $j.fn.addLast = function(types, selector, data, fn){
  406.         return registerHandler.call(this, types, selector, data, fn, true);
  407.     }
  408.    
  409.     /**
  410.      * Just for convenience to allow calling in natural fashion instead of object literal
  411.      * @param {Object} event see jQuery on method
  412.      * @param {Object} selector see jQuery on method
  413.      * @param {Object} data see jQuery on method
  414.      * @param {Object} handler see jQuery on method
  415.      * @param {Object} priority a priority between 10 and -10
  416.      */
  417.     $j.fn.onPrio = function(types, selector, data, fn, priority){
  418.         /**
  419.          * influenced by jQuery.on
  420.          */
  421.         var type;
  422.         // Types can be a map of types/handlers
  423.         if (typeof types === "object") {
  424.             // (types-Object, priority)
  425.             if(typeof selector === "number"){
  426.                 priority = selector;
  427.                 selector = data = undefined;
  428.             }
  429.             // ( types-Object, selector, priority )
  430.             else if (typeof selector === "string" && typeof data === "number") {
  431.                 priority = data;
  432.                 data = undefined;
  433.             }
  434.             // ( types-Object, data, priority )
  435.             else if (typeof selector !== "string" && typeof data === "number") {
  436.                 priority = data;
  437.                 data = selector;
  438.                 selector = undefined;
  439.             }
  440.             // ( types-Object, selector, data, priority )
  441.             else if (typeof fn === "number") {
  442.                 priority = fn;
  443.             }
  444.             for (type in types) {
  445.                 this.onPrio(type, selector, data, types[type], priority);
  446.             }
  447.             return this;
  448.         }
  449.         //function(types, selector, data, fn, priority)
  450.         if (!fn && fn != 0 && !priority) {
  451.             // (types, fn, priority)
  452.             fn = selector;
  453.             priority = data;
  454.             data = selector = undefined;
  455.         }
  456.         else if (!priority) {
  457.             if (typeof selector === "string") {
  458.                 // ( types, selector, fn )
  459.                 priority = fn;
  460.                 fn = data;
  461.                 data = undefined;
  462.             } else {
  463.                 // ( types, data, fn )
  464.                 priority = fn;
  465.                 fn = data;
  466.                 data = selector;
  467.                 selector = undefined;
  468.             }
  469.         }
  470.         if (fn === false) {
  471.             fn = returnFalse;
  472.         } else if (!fn) {
  473.             return this;
  474.         }
  475.         return this.each(function(){
  476.             $j(this).onPriority({'events':types, 'selector':selector, 'data':data, 'fn':fn, 'priority':priority});
  477.         });
  478.     };
  479. })(window, jQuery);
RAW Paste Data