1 // ========================================================================== 2 // Project: SproutCore Costello - Property Observing Library 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 // ........................................................................ 9 // ObserverSet 10 // 11 12 /** 13 @namespace 14 15 This private class is used to store information about observers on a 16 particular key. Note that this object is not observable. You create new 17 instances by calling SC.beget(SC.ObserverSet) ; 18 19 @private 20 @since SproutCore 1.0 21 */ 22 SC.ObserverSet = { 23 24 /** 25 Adds the named target/method observer to the set. The method must be 26 a function, not a string. 27 */ 28 add: function(target, method, context) { 29 var targetGuid = SC.guidFor(target), 30 methodGuid = SC.guidFor(method), 31 targets = this._members, 32 members = this.members, 33 indexes = targets[targetGuid], // get the set of methods 34 index, member; 35 36 if ( !indexes ) indexes = targets[targetGuid] = { _size: 0 }; 37 38 index = indexes[methodGuid]; 39 if (index === undefined) { 40 indexes[methodGuid] = members.length; 41 // Increase the size of the indexes for this target so that it can be cleaned up. 42 indexes._size++; 43 member = [target, method, context]; 44 45 //@if(debug) 46 // If deferred call logging info was specified (i.e., in debug mode when 47 // such logging is enabled), we need to add it to the enqueued target/ 48 // method. 49 member[3] = arguments[3]; 50 //@endif 51 52 members.push(member); 53 } 54 else { 55 //@if(debug) 56 // If deferred call logging info was specified (i.e., in debug mode when 57 // such logging is enabled), we need to add it to the enqueued target/ 58 // method. 59 var loggingInfo = arguments[3], 60 memberLoggingInfo; 61 62 if (loggingInfo) { 63 member = members[index]; 64 memberLoggingInfo = member[3]; 65 if (!memberLoggingInfo) { 66 member[3] = [loggingInfo]; 67 } 68 else if (!(memberLoggingInfo instanceof Array)) { 69 member[3] = [memberLoggingInfo, loggingInfo]; 70 } 71 else { 72 memberLoggingInfo.push(loggingInfo); 73 } 74 } 75 //@endif 76 } 77 }, 78 79 /** 80 removes the named target/method observer from the set. If this is the 81 last method for the named target, then the number of targets will also 82 be reduced. 83 84 returns YES if the items was removed, NO if it was not found. 85 */ 86 remove: function(target, method) { 87 var targetGuid = SC.guidFor(target), methodGuid = SC.guidFor(method); 88 var indexes = this._members[targetGuid], members = this.members; 89 90 if( !indexes ) return false; 91 92 var index = indexes[methodGuid]; 93 if ( index === undefined) return false; 94 95 if (index !== members.length - 1) { 96 var entry = (members[index] = members[members.length - 1]); 97 this._members[SC.guidFor(entry[0])][SC.guidFor(entry[1])] = index; 98 } 99 100 // Throw away the last member (it has been moved or is the member we are removing). 101 members.pop(); 102 103 // Remove the method tracked for the target. 104 delete this._members[targetGuid][methodGuid]; 105 106 // If there are no more methods tracked on the target, remove the target. 107 if (--this._members[targetGuid]._size === 0) { 108 delete this._members[targetGuid]; 109 } 110 111 return true; 112 }, 113 114 /** 115 Invokes the target/method pairs in the receiver. Used by SC.RunLoop 116 Note: does not support context 117 */ 118 invokeMethods: function() { 119 var members = this.members, member; 120 121 //@if(debug) 122 var shouldLog = SC.LOG_DEFERRED_CALLS, 123 target, method, methodName, loggingInfo, loggingInfos, 124 originatingTarget, originatingMethod, originatingMethodName, 125 originatingStack, j, jLen; 126 //@endif 127 128 for( var i=0, l=members.length; i<l; i++ ) { 129 member = members[i]; 130 131 // Call the method (member[1]) on the target (member[0]) with the context (member[2]) 132 member[1].call(member[0], member[2]); 133 134 //@if(debug) 135 // If we have logging info specified for who scheduled the particular 136 // invocation, and logging is enabled, then output it. 137 if (shouldLog) { 138 target = member[0]; 139 method = member[1]; 140 methodName = method.displayName || method; 141 loggingInfo = member[3]; 142 if (loggingInfo) { 143 // If the logging info is not an array, that means only one place 144 // scheduled the invocation. 145 if (!(loggingInfo instanceof Array)) { 146 // We'll treat single-scheduler cases specially to make the output 147 // better for the user, even if it means some essentially-duplicated 148 // code. 149 originatingTarget = loggingInfo.originatingTarget; 150 originatingMethod = loggingInfo.originatingMethod; 151 originatingStack = loggingInfo.originatingStack; 152 originatingMethodName = (originatingMethod ? originatingMethod.displayName : "(unknown)") || originatingMethod; 153 SC.Logger.log("Invoking runloop-scheduled method %@ on %@. Originated by target %@, method %@, stack: ".fmt(methodName, target, originatingTarget, originatingMethodName), originatingStack); 154 } 155 else { 156 SC.Logger.log("Invoking runloop-scheduled method %@ on %@, which was scheduled by multiple target/method pairs:".fmt(methodName, target)); 157 loggingInfos = loggingInfo; 158 for (j = 0, jLen = loggingInfos.length; j < jLen; ++j) { 159 loggingInfo = loggingInfos[j]; 160 originatingTarget = loggingInfo.originatingTarget; 161 originatingMethod = loggingInfo.originatingMethod; 162 originatingStack = loggingInfo.originatingStack; 163 originatingMethodName = (originatingMethod ? originatingMethod.displayName : "(unknown)") || originatingMethod; 164 SC.Logger.log(" [%@] originated by target %@, method %@, stack:".fmt(j, originatingTarget, originatingMethodName), originatingStack); 165 } 166 } 167 } 168 else { 169 // If we didn't capture information for this invocation, just report 170 // what we can. 171 SC.Logger.log("Invoking runloop-scheduled method %@ on %@, but we didn’t capture information about who scheduled it…".fmt(methodName, target)); 172 } 173 } 174 //@endif 175 } 176 }, 177 178 /** 179 Returns a new instance of the set with the contents cloned. 180 */ 181 clone: function() { 182 var newSet = SC.ObserverSet.create(), memberArray = this.members; 183 184 newSet._members = SC.clone(this._members); 185 var newMembers = newSet.members; 186 187 for( var i=0, l=memberArray.length; i<l; i++ ) { 188 newMembers[i] = SC.clone(memberArray[i]); 189 newMembers[i].length = 3; 190 //@if(debug) 191 newMembers[i].length = 4; 192 //@endif 193 } 194 195 return newSet; 196 }, 197 198 /** 199 Creates a new instance of the observer set. 200 */ 201 create: function() { 202 return new SC.ObserverSet.constructor(); 203 }, 204 205 getMembers: function() { 206 return this.members.slice(0); 207 }, 208 209 constructor: function() { 210 this._members = {}; 211 this.members = []; 212 } 213 214 } ; 215 216 SC.ObserverSet.constructor.prototype = SC.ObserverSet; 217 SC.ObserverSet.slice = SC.ObserverSet.clone; 218 SC.ObserverSet.copy = SC.ObserverSet.clone; 219 220