Copybara bot | be50d49 | 2023-11-30 00:16:42 +0100 | [diff] [blame] | 1 | define( [ |
| 2 | "./core", |
| 3 | "./core/toType", |
| 4 | "./var/isFunction", |
| 5 | "./var/rnothtmlwhite" |
| 6 | ], function( jQuery, toType, isFunction, rnothtmlwhite ) { |
| 7 | |
| 8 | "use strict"; |
| 9 | |
| 10 | // Convert String-formatted options into Object-formatted ones |
| 11 | function createOptions( options ) { |
| 12 | var object = {}; |
| 13 | jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { |
| 14 | object[ flag ] = true; |
| 15 | } ); |
| 16 | return object; |
| 17 | } |
| 18 | |
| 19 | /* |
| 20 | * Create a callback list using the following parameters: |
| 21 | * |
| 22 | * options: an optional list of space-separated options that will change how |
| 23 | * the callback list behaves or a more traditional option object |
| 24 | * |
| 25 | * By default a callback list will act like an event callback list and can be |
| 26 | * "fired" multiple times. |
| 27 | * |
| 28 | * Possible options: |
| 29 | * |
| 30 | * once: will ensure the callback list can only be fired once (like a Deferred) |
| 31 | * |
| 32 | * memory: will keep track of previous values and will call any callback added |
| 33 | * after the list has been fired right away with the latest "memorized" |
| 34 | * values (like a Deferred) |
| 35 | * |
| 36 | * unique: will ensure a callback can only be added once (no duplicate in the list) |
| 37 | * |
| 38 | * stopOnFalse: interrupt callings when a callback returns false |
| 39 | * |
| 40 | */ |
| 41 | jQuery.Callbacks = function( options ) { |
| 42 | |
| 43 | // Convert options from String-formatted to Object-formatted if needed |
| 44 | // (we check in cache first) |
| 45 | options = typeof options === "string" ? |
| 46 | createOptions( options ) : |
| 47 | jQuery.extend( {}, options ); |
| 48 | |
| 49 | var // Flag to know if list is currently firing |
| 50 | firing, |
| 51 | |
| 52 | // Last fire value for non-forgettable lists |
| 53 | memory, |
| 54 | |
| 55 | // Flag to know if list was already fired |
| 56 | fired, |
| 57 | |
| 58 | // Flag to prevent firing |
| 59 | locked, |
| 60 | |
| 61 | // Actual callback list |
| 62 | list = [], |
| 63 | |
| 64 | // Queue of execution data for repeatable lists |
| 65 | queue = [], |
| 66 | |
| 67 | // Index of currently firing callback (modified by add/remove as needed) |
| 68 | firingIndex = -1, |
| 69 | |
| 70 | // Fire callbacks |
| 71 | fire = function() { |
| 72 | |
| 73 | // Enforce single-firing |
| 74 | locked = locked || options.once; |
| 75 | |
| 76 | // Execute callbacks for all pending executions, |
| 77 | // respecting firingIndex overrides and runtime changes |
| 78 | fired = firing = true; |
| 79 | for ( ; queue.length; firingIndex = -1 ) { |
| 80 | memory = queue.shift(); |
| 81 | while ( ++firingIndex < list.length ) { |
| 82 | |
| 83 | // Run callback and check for early termination |
| 84 | if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && |
| 85 | options.stopOnFalse ) { |
| 86 | |
| 87 | // Jump to end and forget the data so .add doesn't re-fire |
| 88 | firingIndex = list.length; |
| 89 | memory = false; |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | // Forget the data if we're done with it |
| 95 | if ( !options.memory ) { |
| 96 | memory = false; |
| 97 | } |
| 98 | |
| 99 | firing = false; |
| 100 | |
| 101 | // Clean up if we're done firing for good |
| 102 | if ( locked ) { |
| 103 | |
| 104 | // Keep an empty list if we have data for future add calls |
| 105 | if ( memory ) { |
| 106 | list = []; |
| 107 | |
| 108 | // Otherwise, this object is spent |
| 109 | } else { |
| 110 | list = ""; |
| 111 | } |
| 112 | } |
| 113 | }, |
| 114 | |
| 115 | // Actual Callbacks object |
| 116 | self = { |
| 117 | |
| 118 | // Add a callback or a collection of callbacks to the list |
| 119 | add: function() { |
| 120 | if ( list ) { |
| 121 | |
| 122 | // If we have memory from a past run, we should fire after adding |
| 123 | if ( memory && !firing ) { |
| 124 | firingIndex = list.length - 1; |
| 125 | queue.push( memory ); |
| 126 | } |
| 127 | |
| 128 | ( function add( args ) { |
| 129 | jQuery.each( args, function( _, arg ) { |
| 130 | if ( isFunction( arg ) ) { |
| 131 | if ( !options.unique || !self.has( arg ) ) { |
| 132 | list.push( arg ); |
| 133 | } |
| 134 | } else if ( arg && arg.length && toType( arg ) !== "string" ) { |
| 135 | |
| 136 | // Inspect recursively |
| 137 | add( arg ); |
| 138 | } |
| 139 | } ); |
| 140 | } )( arguments ); |
| 141 | |
| 142 | if ( memory && !firing ) { |
| 143 | fire(); |
| 144 | } |
| 145 | } |
| 146 | return this; |
| 147 | }, |
| 148 | |
| 149 | // Remove a callback from the list |
| 150 | remove: function() { |
| 151 | jQuery.each( arguments, function( _, arg ) { |
| 152 | var index; |
| 153 | while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { |
| 154 | list.splice( index, 1 ); |
| 155 | |
| 156 | // Handle firing indexes |
| 157 | if ( index <= firingIndex ) { |
| 158 | firingIndex--; |
| 159 | } |
| 160 | } |
| 161 | } ); |
| 162 | return this; |
| 163 | }, |
| 164 | |
| 165 | // Check if a given callback is in the list. |
| 166 | // If no argument is given, return whether or not list has callbacks attached. |
| 167 | has: function( fn ) { |
| 168 | return fn ? |
| 169 | jQuery.inArray( fn, list ) > -1 : |
| 170 | list.length > 0; |
| 171 | }, |
| 172 | |
| 173 | // Remove all callbacks from the list |
| 174 | empty: function() { |
| 175 | if ( list ) { |
| 176 | list = []; |
| 177 | } |
| 178 | return this; |
| 179 | }, |
| 180 | |
| 181 | // Disable .fire and .add |
| 182 | // Abort any current/pending executions |
| 183 | // Clear all callbacks and values |
| 184 | disable: function() { |
| 185 | locked = queue = []; |
| 186 | list = memory = ""; |
| 187 | return this; |
| 188 | }, |
| 189 | disabled: function() { |
| 190 | return !list; |
| 191 | }, |
| 192 | |
| 193 | // Disable .fire |
| 194 | // Also disable .add unless we have memory (since it would have no effect) |
| 195 | // Abort any pending executions |
| 196 | lock: function() { |
| 197 | locked = queue = []; |
| 198 | if ( !memory && !firing ) { |
| 199 | list = memory = ""; |
| 200 | } |
| 201 | return this; |
| 202 | }, |
| 203 | locked: function() { |
| 204 | return !!locked; |
| 205 | }, |
| 206 | |
| 207 | // Call all callbacks with the given context and arguments |
| 208 | fireWith: function( context, args ) { |
| 209 | if ( !locked ) { |
| 210 | args = args || []; |
| 211 | args = [ context, args.slice ? args.slice() : args ]; |
| 212 | queue.push( args ); |
| 213 | if ( !firing ) { |
| 214 | fire(); |
| 215 | } |
| 216 | } |
| 217 | return this; |
| 218 | }, |
| 219 | |
| 220 | // Call all the callbacks with the given arguments |
| 221 | fire: function() { |
| 222 | self.fireWith( this, arguments ); |
| 223 | return this; |
| 224 | }, |
| 225 | |
| 226 | // To know if the callbacks have already been called at least once |
| 227 | fired: function() { |
| 228 | return !!fired; |
| 229 | } |
| 230 | }; |
| 231 | |
| 232 | return self; |
| 233 | }; |
| 234 | |
| 235 | return jQuery; |
| 236 | } ); |