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 sc_require('system/function');
  9 
 10 SC.mixin(Function.prototype,
 11 /** @lends Function.prototype */ {
 12 
 13   /**
 14     Indicates that the function should be treated as a computed property.
 15 
 16     Computed properties are methods that you want to treat as if they were
 17     static properties.  When you use get() or set() on a computed property,
 18     the object will call the property method and return its value instead of
 19     returning the method itself.  This makes it easy to create "virtual
 20     properties" that are computed dynamically from other properties.
 21 
 22     Consider the following example:
 23 
 24           contact = SC.Object.create({
 25 
 26             firstName: "Charles",
 27             lastName: "Jolley",
 28 
 29             // This is a computed property!
 30             fullName: function() {
 31               return this.getEach('firstName','lastName').compact().join(' ') ;
 32             }.property('firstName', 'lastName'),
 33 
 34             // this is not
 35             getFullName: function() {
 36               return this.getEach('firstName','lastName').compact().join(' ') ;
 37             }
 38           });
 39 
 40           contact.get('firstName') ;
 41           --> "Charles"
 42 
 43           contact.get('fullName') ;
 44           --> "Charles Jolley"
 45 
 46           contact.get('getFullName') ;
 47           --> function()
 48 
 49     Note that when you get the fullName property, SproutCore will call the
 50     fullName() function and return its value whereas when you get() a property
 51     that contains a regular method (such as getFullName above), then the
 52     function itself will be returned instead.
 53 
 54     Using Dependent Keys
 55     ----
 56 
 57     Computed properties are often computed dynamically from other member
 58     properties.  Whenever those properties change, you need to notify any
 59     object that is observing the computed property that the computed property
 60     has changed also.  We call these properties the computed property is based
 61     upon "dependent keys".
 62 
 63     For example, in the contact object above, the fullName property depends on
 64     the firstName and lastName property.  If either property value changes,
 65     any observer watching the fullName property will need to be notified as
 66     well.
 67 
 68     You inform SproutCore of these dependent keys by passing the key names
 69     as parameters to the property() function.  Whenever the value of any key
 70     you name here changes, the computed property will be marked as changed
 71     also.
 72 
 73     You should always register dependent keys for computed properties to
 74     ensure they update.
 75 
 76     Sometimes you may need to depend on keys that are several objects deep. In
 77     that case, you can provide a path to property():
 78 
 79         capitalizedName: function() {
 80           return this.getPath('person.fullName').toUpper();
 81         }.property('person.fullName')
 82 
 83     This will cause observers of +capitalizedName+ to be fired when either
 84     +fullName+ _or_ +person+ changes.
 85 
 86     Using Computed Properties as Setters
 87     ---
 88 
 89     Computed properties can be used to modify the state of an object as well
 90     as to return a value.  Unlike many other key-value system, you use the
 91     same method to both get and set values on a computed property.  To
 92     write a setter, simply declare two extra parameters: key and value.
 93 
 94     Whenever your property function is called as a setter, the value
 95     parameter will be set.  Whenever your property is called as a getter the
 96     value parameter will be undefined.
 97 
 98     For example, the following object will split any full name that you set
 99     into a first name and last name components and save them.
100 
101           contact = SC.Object.create({
102 
103             fullName: function(key, value) {
104               if (value !== undefined) {
105                 var parts = value.split(' ') ;
106                 this.beginPropertyChanges()
107                   .set('firstName', parts[0])
108                   .set('lastName', parts[1])
109                 .endPropertyChanges() ;
110               }
111               return this.getEach('firstName', 'lastName').compact().join(' ');
112             }.property('firstName','lastName')
113 
114           }) ;
115 
116     Why Use The Same Method for Getters and Setters?
117     ---
118 
119     Most property-based frameworks expect you to write two methods for each
120     property but SproutCore only uses one. We do this because most of the time
121     when you write a setter is is basically a getter plus some extra work.
122     There is little added benefit in writing both methods when you can
123     conditionally exclude part of it. This helps to keep your code more
124     compact and easier to maintain.
125 
126     @param {String...} dependentKeys optional set of dependent keys
127     @returns {Function} the declared function instance
128   */
129   property: function() {
130     return SC.Function.property(this, arguments);
131   },
132 
133   /**
134     You can call this method on a computed property to indicate that the
135     property is cacheable (or not cacheable).  By default all computed
136     properties are not cached.  Enabling this feature will allow SproutCore
137     to cache the return value of your computed property and to use that
138     value until one of your dependent properties changes or until you
139     invoke propertyDidChange() and name the computed property itself.
140 
141     If you do not specify this option, computed properties are assumed to be
142     not cacheable.
143 
144     @param {Boolean} aFlag optionally indicate cacheable or no, default YES
145     @returns {Function} receiver, useful for chaining calls.
146   */
147   cacheable: function(aFlag) {
148     return SC.Function.cacheable(this, aFlag);
149   },
150 
151   /**
152     Indicates that the computed property is volatile.  Normally SproutCore
153     assumes that your computed property is idempotent.  That is, calling
154     set() on your property more than once with the same value has the same
155     effect as calling it only once.
156 
157     All non-computed properties are idempotent and normally you should make
158     your computed properties behave the same way.  However, if you need to
159     make your property change its return value every time your method is
160     called, you may chain this to your property to make it volatile.
161 
162     If you do not specify this option, properties are assumed to be
163     non-volatile.
164 
165     @param {Boolean} aFlag optionally indicate state, default to YES
166     @returns {Function} receiver, useful for chaining calls.
167   */
168   idempotent: function(aFlag) {
169     return SC.Function.idempotent(this, aFlag);
170   },
171 
172   enhance: function() {
173     return SC.Function.enhance(this);
174   },
175 
176   /**
177     Declare that a function should observe an object or property at the named
178     path.  Note that the path is used only to construct the observation one time.
179 
180     @param {String...} propertyPaths A list of strings which indicate the
181       properties being observed
182 
183     @returns {Function} receiver, useful for chaining calls.
184   */
185   observes: function(propertyPaths) {
186     return SC.Function.observes(this, arguments);
187   }
188 
189 });
190