Skip to content

Instantly share code, notes, and snippets.

@ColinCampbell
Created April 12, 2011 00:57
Show Gist options
  • Select an option

  • Save ColinCampbell/914708 to your computer and use it in GitHub Desktop.

Select an option

Save ColinCampbell/914708 to your computer and use it in GitHub Desktop.
diff --git a/frameworks/runtime/core.js b/frameworks/runtime/core.js
index ce3660c..871ca7a 100644
--- a/frameworks/runtime/core.js
+++ b/frameworks/runtime/core.js
@@ -785,333 +785,3 @@ SC.ORDER_DEFINITION = [ SC.T_ERROR,
SC.T_OBJECT,
SC.T_FUNCTION,
SC.T_CLASS ];
-
-
-// ........................................
-// FUNCTION ENHANCEMENTS
-//
-
-SC.Function = {
- property: function(fn, keys) {
- fn.dependentKeys = SC.$A(keys) ;
- var guid = SC.guidFor(fn) ;
- fn.cacheKey = "__cache__" + guid ;
- fn.lastSetValueKey = "__lastValue__" + guid ;
- fn.isProperty = true ;
- return fn ;
- },
-
- cacheable: function(fn, aFlag) {
- fn.isProperty = true ; // also make a property just in case
- if (!fn.dependentKeys) fn.dependentKeys = [] ;
- fn.isCacheable = (aFlag === undefined) ? true : aFlag ;
- return fn ;
- },
-
- idempotent: function(fn, aFlag) {
- fn.isProperty = true; // also make a property just in case
- if (!fn.dependentKeys) this.dependentKeys = [] ;
- fn.isVolatile = (aFlag === undefined) ? true : aFlag ;
- return fn ;
- },
-
- enhance: function(fn) {
- fn.isEnhancement = true;
- return fn ;
- },
-
- observes: function(fn, propertyPaths) {
- // sort property paths into local paths (i.e just a property name) and
- // full paths (i.e. those with a . or * in them)
- var loc = propertyPaths.length, local = null, paths = null ;
- while(--loc >= 0) {
- var path = propertyPaths[loc] ;
- // local
- if ((path.indexOf('.')<0) && (path.indexOf('*')<0)) {
- if (!local) local = fn.localPropertyPaths = [] ;
- local.push(path);
-
- // regular
- } else {
- if (!paths) paths = fn.propertyPaths = [] ;
- paths.push(path) ;
- }
- }
- return fn ;
- }
-
-};
-
-SC.mixin(Function.prototype,
-/** @lends Function.prototype */ {
-
- /**
- Indicates that the function should be treated as a computed property.
-
- Computed properties are methods that you want to treat as if they were
- static properties. When you use get() or set() on a computed property,
- the object will call the property method and return its value instead of
- returning the method itself. This makes it easy to create "virtual
- properties" that are computed dynamically from other properties.
-
- Consider the following example:
-
- contact = SC.Object.create({
-
- firstName: "Charles",
- lastName: "Jolley",
-
- // This is a computed property!
- fullName: function() {
- return this.getEach('firstName','lastName').compact().join(' ') ;
- }.property('firstName', 'lastName'),
-
- // this is not
- getFullName: function() {
- return this.getEach('firstName','lastName').compact().join(' ') ;
- }
- });
-
- contact.get('firstName') ;
- --> "Charles"
-
- contact.get('fullName') ;
- --> "Charles Jolley"
-
- contact.get('getFullName') ;
- --> function()
-
- Note that when you get the fullName property, SproutCore will call the
- fullName() function and return its value whereas when you get() a property
- that contains a regular method (such as getFullName above), then the
- function itself will be returned instead.
-
- Using Dependent Keys
- ----
-
- Computed properties are often computed dynamically from other member
- properties. Whenever those properties change, you need to notify any
- object that is observing the computed property that the computed property
- has changed also. We call these properties the computed property is based
- upon "dependent keys".
-
- For example, in the contact object above, the fullName property depends on
- the firstName and lastName property. If either property value changes,
- any observer watching the fullName property will need to be notified as
- well.
-
- You inform SproutCore of these dependent keys by passing the key names
- as parameters to the property() function. Whenever the value of any key
- you name here changes, the computed property will be marked as changed
- also.
-
- You should always register dependent keys for computed properties to
- ensure they update.
-
- Sometimes you may need to depend on keys that are several objects deep. In
- that case, you can provide a path to property():
-
- capitalizedName: function() {
- return this.getPath('person.fullName').toUpper();
- }.property('person.firstName')
-
- This will cause observers of +capitalizedName+ to be fired when either
- +fullName+ _or_ +person+ changes.
-
- Using Computed Properties as Setters
- ---
-
- Computed properties can be used to modify the state of an object as well
- as to return a value. Unlike many other key-value system, you use the
- same method to both get and set values on a computed property. To
- write a setter, simply declare two extra parameters: key and value.
-
- Whenever your property function is called as a setter, the value
- parameter will be set. Whenever your property is called as a getter the
- value parameter will be undefined.
-
- For example, the following object will split any full name that you set
- into a first name and last name components and save them.
-
- contact = SC.Object.create({
-
- fullName: function(key, value) {
- if (value !== undefined) {
- var parts = value.split(' ') ;
- this.beginPropertyChanges()
- .set('firstName', parts[0])
- .set('lastName', parts[1])
- .endPropertyChanges() ;
- }
- return this.getEach('firstName', 'lastName').compact().join(' ');
- }.property('firstName','lastName')
-
- }) ;
-
- Why Use The Same Method for Getters and Setters?
- ---
-
- Most property-based frameworks expect you to write two methods for each
- property but SproutCore only uses one. We do this because most of the time
- when you write a setter is is basically a getter plus some extra work.
- There is little added benefit in writing both methods when you can
- conditionally exclude part of it. This helps to keep your code more
- compact and easier to maintain.
-
- @param {String...} dependentKeys optional set of dependent keys
- @returns {Function} the declared function instance
- */
- property: function() {
- return SC.Function.property(this, arguments);
- },
-
- /**
- You can call this method on a computed property to indicate that the
- property is cacheable (or not cacheable). By default all computed
- properties are not cached. Enabling this feature will allow SproutCore
- to cache the return value of your computed property and to use that
- value until one of your dependent properties changes or until you
- invoke propertyDidChange() and name the computed property itself.
-
- If you do not specify this option, computed properties are assumed to be
- not cacheable.
-
- @param {Boolean} aFlag optionally indicate cacheable or no, default YES
- @returns {Function} reciever, useful for chaining calls.
- */
- cacheable: function(aFlag) {
- return SC.Function.cacheable(this, aFlag);
- },
-
- /**
- Indicates that the computed property is volatile. Normally SproutCore
- assumes that your computed property is idempotent. That is, calling
- set() on your property more than once with the same value has the same
- effect as calling it only once.
-
- All non-computed properties are idempotent and normally you should make
- your computed properties behave the same way. However, if you need to
- make your property change its return value everytime your method is
- called, you may chain this to your property to make it volatile.
-
- If you do not specify this option, properties are assumed to be
- non-volatile.
-
- @param {Boolean} aFlag optionally indicate state, default to YES
- @returns {Function} reciever, useful for chaining calls.
- */
- idempotent: function(aFlag) {
- return SC.Function.idempotent(this, aFlag);
- },
-
- enhance: function() {
- return SC.Function.enhance(this);
- },
-
- /**
- Declare that a function should observe an object or property at the named
- path. Note that the path is used only to construct the observation one time.
-
- @param {String...} propertyPaths A list of strings which indicate the
- properties being observed
-
- @returns {Function} reciever, useful for chaining calls.
- */
- observes: function(propertyPaths) {
- return SC.Function.observes(this, arguments);
- }
-
-});
-
-/**
- @class
-
- Implements support methods useful when working with strings in SproutCore
- applications.
-*/
-SC.String = /** @scope SC.String.prototype */ {
-
- // Interpolate string. looks for %@ or %@1; to control the order of params.
- /**
- Apply formatting options to the string. This will look for occurrences
- of %@ in your string and substitute them with the arguments you pass into
- this method. If you want to control the specific order of replacement,
- you can add a number after the key as well to indicate which argument
- you want to insert.
-
- Ordered insertions are most useful when building loc strings where values
- you need to insert may appear in different orders.
-
- Examples
- -----
-
- "Hello %@ %@".fmt('John', 'Doe') => "Hello John Doe"
- "Hello %@2, %@1".fmt('John', 'Doe') => "Hello Doe, John"
-
- @param {Object...} args optional arguments
- @returns {String} formatted string
- */
- fmt: function(str, formats) {
- // first, replace any ORDERED replacements.
- var idx = 0; // the current index for non-numerical replacements
- return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
- argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ;
- s = formats[argIndex];
- return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
- }) ;
- },
-
- /**
- Splits the string into words, separated by spaces. Empty strings are
- removed from the results.
-
- @returns {Array} An array of non-empty strings
- */
- w: function(str) {
- var ary = [], ary2 = str.split(' '), len = ary2.length, string, idx=0;
- for (idx=0; idx<len; ++idx) {
- string = ary2[idx] ;
- if (string.length !== 0) ary.push(string) ; // skip empty strings
- }
- return ary ;
- }
-};
-
-// ..........................................................
-// STRING ENHANCEMENT
-//
-
-/**
- @namespace
- Extends the String class by adding a few helpful methods.
-*/
-SC.mixin(String.prototype,
-/** @scope String.prototype */{
-
- /**
- @see SC.String.fmt
- */
- fmt: function() {
- return SC.String.fmt(this, arguments);
- },
-
- /**
- @see SC.String.w
- */
- w: function() {
- return SC.String.w(this);
- }
-});
-
-//
-// DATE ENHANCEMENT
-//
-if (!Date.now) {
- /**
- @ignore
- */
- Date.now = function() {
- return new Date().getTime() ;
- };
-}
-
diff --git a/frameworks/runtime/ext/date.js b/frameworks/runtime/ext/date.js
new file mode 100644
index 0000000..b0e2855
--- /dev/null
+++ b/frameworks/runtime/ext/date.js
@@ -0,0 +1,15 @@
+// ==========================================================================
+// Project: SproutCore Costello - Property Observing Library
+// Copyright: ©2006-2011 Strobe Inc. and contributors.
+// Portions ©2008-2011 Apple Inc. All rights reserved.
+// License: Licensed under MIT license (see license.js)
+// ==========================================================================
+
+if (!Date.now) {
+ /**
+ @ignore
+ */
+ Date.now = function() {
+ return new Date().getTime() ;
+ };
+}
diff --git a/frameworks/runtime/ext/function.js b/frameworks/runtime/ext/function.js
new file mode 100644
index 0000000..ecd2a1e
--- /dev/null
+++ b/frameworks/runtime/ext/function.js
@@ -0,0 +1,189 @@
+// ==========================================================================
+// Project: SproutCore Costello - Property Observing Library
+// Copyright: ©2006-2011 Strobe Inc. and contributors.
+// Portions ©2008-2011 Apple Inc. All rights reserved.
+// License: Licensed under MIT license (see license.js)
+// ==========================================================================
+
+sc_require('system/function');
+
+SC.mixin(Function.prototype,
+/** @lends Function.prototype */ {
+
+ /**
+ Indicates that the function should be treated as a computed property.
+
+ Computed properties are methods that you want to treat as if they were
+ static properties. When you use get() or set() on a computed property,
+ the object will call the property method and return its value instead of
+ returning the method itself. This makes it easy to create "virtual
+ properties" that are computed dynamically from other properties.
+
+ Consider the following example:
+
+ contact = SC.Object.create({
+
+ firstName: "Charles",
+ lastName: "Jolley",
+
+ // This is a computed property!
+ fullName: function() {
+ return this.getEach('firstName','lastName').compact().join(' ') ;
+ }.property('firstName', 'lastName'),
+
+ // this is not
+ getFullName: function() {
+ return this.getEach('firstName','lastName').compact().join(' ') ;
+ }
+ });
+
+ contact.get('firstName') ;
+ --> "Charles"
+
+ contact.get('fullName') ;
+ --> "Charles Jolley"
+
+ contact.get('getFullName') ;
+ --> function()
+
+ Note that when you get the fullName property, SproutCore will call the
+ fullName() function and return its value whereas when you get() a property
+ that contains a regular method (such as getFullName above), then the
+ function itself will be returned instead.
+
+ Using Dependent Keys
+ ----
+
+ Computed properties are often computed dynamically from other member
+ properties. Whenever those properties change, you need to notify any
+ object that is observing the computed property that the computed property
+ has changed also. We call these properties the computed property is based
+ upon "dependent keys".
+
+ For example, in the contact object above, the fullName property depends on
+ the firstName and lastName property. If either property value changes,
+ any observer watching the fullName property will need to be notified as
+ well.
+
+ You inform SproutCore of these dependent keys by passing the key names
+ as parameters to the property() function. Whenever the value of any key
+ you name here changes, the computed property will be marked as changed
+ also.
+
+ You should always register dependent keys for computed properties to
+ ensure they update.
+
+ Sometimes you may need to depend on keys that are several objects deep. In
+ that case, you can provide a path to property():
+
+ capitalizedName: function() {
+ return this.getPath('person.fullName').toUpper();
+ }.property('person.firstName')
+
+ This will cause observers of +capitalizedName+ to be fired when either
+ +fullName+ _or_ +person+ changes.
+
+ Using Computed Properties as Setters
+ ---
+
+ Computed properties can be used to modify the state of an object as well
+ as to return a value. Unlike many other key-value system, you use the
+ same method to both get and set values on a computed property. To
+ write a setter, simply declare two extra parameters: key and value.
+
+ Whenever your property function is called as a setter, the value
+ parameter will be set. Whenever your property is called as a getter the
+ value parameter will be undefined.
+
+ For example, the following object will split any full name that you set
+ into a first name and last name components and save them.
+
+ contact = SC.Object.create({
+
+ fullName: function(key, value) {
+ if (value !== undefined) {
+ var parts = value.split(' ') ;
+ this.beginPropertyChanges()
+ .set('firstName', parts[0])
+ .set('lastName', parts[1])
+ .endPropertyChanges() ;
+ }
+ return this.getEach('firstName', 'lastName').compact().join(' ');
+ }.property('firstName','lastName')
+
+ }) ;
+
+ Why Use The Same Method for Getters and Setters?
+ ---
+
+ Most property-based frameworks expect you to write two methods for each
+ property but SproutCore only uses one. We do this because most of the time
+ when you write a setter is is basically a getter plus some extra work.
+ There is little added benefit in writing both methods when you can
+ conditionally exclude part of it. This helps to keep your code more
+ compact and easier to maintain.
+
+ @param {String...} dependentKeys optional set of dependent keys
+ @returns {Function} the declared function instance
+ */
+ property: function() {
+ return SC.Function.property(this, arguments);
+ },
+
+ /**
+ You can call this method on a computed property to indicate that the
+ property is cacheable (or not cacheable). By default all computed
+ properties are not cached. Enabling this feature will allow SproutCore
+ to cache the return value of your computed property and to use that
+ value until one of your dependent properties changes or until you
+ invoke propertyDidChange() and name the computed property itself.
+
+ If you do not specify this option, computed properties are assumed to be
+ not cacheable.
+
+ @param {Boolean} aFlag optionally indicate cacheable or no, default YES
+ @returns {Function} reciever, useful for chaining calls.
+ */
+ cacheable: function(aFlag) {
+ return SC.Function.cacheable(this, aFlag);
+ },
+
+ /**
+ Indicates that the computed property is volatile. Normally SproutCore
+ assumes that your computed property is idempotent. That is, calling
+ set() on your property more than once with the same value has the same
+ effect as calling it only once.
+
+ All non-computed properties are idempotent and normally you should make
+ your computed properties behave the same way. However, if you need to
+ make your property change its return value everytime your method is
+ called, you may chain this to your property to make it volatile.
+
+ If you do not specify this option, properties are assumed to be
+ non-volatile.
+
+ @param {Boolean} aFlag optionally indicate state, default to YES
+ @returns {Function} reciever, useful for chaining calls.
+ */
+ idempotent: function(aFlag) {
+ return SC.Function.idempotent(this, aFlag);
+ },
+
+ enhance: function() {
+ return SC.Function.enhance(this);
+ },
+
+ /**
+ Declare that a function should observe an object or property at the named
+ path. Note that the path is used only to construct the observation one time.
+
+ @param {String...} propertyPaths A list of strings which indicate the
+ properties being observed
+
+ @returns {Function} reciever, useful for chaining calls.
+ */
+ observes: function(propertyPaths) {
+ return SC.Function.observes(this, arguments);
+ }
+
+});
diff --git a/frameworks/runtime/ext/string.js b/frameworks/runtime/ext/string.js
new file mode 100644
index 0000000..81661ff
--- /dev/null
+++ b/frameworks/runtime/ext/string.js
@@ -0,0 +1,31 @@
+// ==========================================================================
+// Project: SproutCore Costello - Property Observing Library
+// Copyright: ©2006-2011 Strobe Inc. and contributors.
+// Portions ©2008-2011 Apple Inc. All rights reserved.
+// License: Licensed under MIT license (see license.js)
+// ==========================================================================
+
+sc_require('system/string');
+
+/**
+ @namespace
+ Extends String by adding a few helpful methods.
+*/
+SC.mixin(String.prototype,
+/** @scope String.prototype */ {
+
+ /**
+ @see SC.String.fmt
+ */
+ fmt: function() {
+ return SC.String.fmt(this, arguments);
+ },
+
+ /**
+ @see SC.String.w
+ */
+ w: function() {
+ return SC.String.w(this);
+ }
+
+});
diff --git a/frameworks/runtime/system/function.js b/frameworks/runtime/system/function.js
new file mode 100644
index 0000000..9cb1e0a
--- /dev/null
+++ b/frameworks/runtime/system/function.js
@@ -0,0 +1,76 @@
+// ==========================================================================
+// Project: SproutCore Costello - Property Observing Library
+// Copyright: ©2006-2011 Strobe Inc. and contributors.
+// Portions ©2008-2011 Apple Inc. All rights reserved.
+// License: Licensed under MIT license (see license.js)
+// ==========================================================================
+
+/**
+ @class
+*/
+SC.Function = /** @scope SC.Function.prototype */{
+
+ /**
+ @see Function.prototype.property
+ */
+ property: function(fn, keys) {
+ fn.dependentKeys = SC.$A(keys) ;
+ var guid = SC.guidFor(fn) ;
+ fn.cacheKey = "__cache__" + guid ;
+ fn.lastSetValueKey = "__lastValue__" + guid ;
+ fn.isProperty = true ;
+ return fn ;
+ },
+
+ /**
+ @see Function.prototype.cacheable
+ */
+ cacheable: function(fn, aFlag) {
+ fn.isProperty = true ; // also make a property just in case
+ if (!fn.dependentKeys) fn.dependentKeys = [] ;
+ fn.isCacheable = (aFlag === undefined) ? true : aFlag ;
+ return fn ;
+ },
+
+ /**
+ @see Function.prototype.idempotent
+ */
+ idempotent: function(fn, aFlag) {
+ fn.isProperty = true; // also make a property just in case
+ if (!fn.dependentKeys) this.dependentKeys = [] ;
+ fn.isVolatile = (aFlag === undefined) ? true : aFlag ;
+ return fn ;
+ },
+
+ /**
+ @see Function.prototype.enhance
+ */
+ enhance: function(fn) {
+ fn.isEnhancement = true;
+ return fn ;
+ },
+
+ /**
+ @see Function.prototype.observes
+ */
+ observes: function(fn, propertyPaths) {
+ // sort property paths into local paths (i.e just a property name) and
+ // full paths (i.e. those with a . or * in them)
+ var loc = propertyPaths.length, local = null, paths = null ;
+ while(--loc >= 0) {
+ var path = propertyPaths[loc] ;
+ // local
+ if ((path.indexOf('.')<0) && (path.indexOf('*')<0)) {
+ if (!local) local = fn.localPropertyPaths = [] ;
+ local.push(path);
+
+ // regular
+ } else {
+ if (!paths) paths = fn.propertyPaths = [] ;
+ paths.push(path) ;
+ }
+ }
+ return fn ;
+ }
+
+};
diff --git a/frameworks/runtime/system/string.js b/frameworks/runtime/system/string.js
new file mode 100644
index 0000000..956788f
--- /dev/null
+++ b/frameworks/runtime/system/string.js
@@ -0,0 +1,60 @@
+// ==========================================================================
+// Project: SproutCore Costello - Property Observing Library
+// Copyright: ©2006-2011 Strobe Inc. and contributors.
+// Portions ©2008-2011 Apple Inc. All rights reserved.
+// License: Licensed under MIT license (see license.js)
+// ==========================================================================
+
+/**
+ @class
+
+ Implements support methods useful when working with strings in SproutCore
+ applications.
+*/
+SC.String = /** @scope SC.String.prototype */ {
+
+ // Interpolate string. looks for %@ or %@1; to control the order of params.
+ /**
+ Apply formatting options to the string. This will look for occurrences
+ of %@ in your string and substitute them with the arguments you pass into
+ this method. If you want to control the specific order of replacement,
+ you can add a number after the key as well to indicate which argument
+ you want to insert.
+
+ Ordered insertions are most useful when building loc strings where values
+ you need to insert may appear in different orders.
+
+ Examples
+ -----
+
+ "Hello %@ %@".fmt('John', 'Doe') => "Hello John Doe"
+ "Hello %@2, %@1".fmt('John', 'Doe') => "Hello Doe, John"
+
+ @param {Object...} args optional arguments
+ @returns {String} formatted string
+ */
+ fmt: function(str, formats) {
+ // first, replace any ORDERED replacements.
+ var idx = 0; // the current index for non-numerical replacements
+ return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
+ argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ;
+ s = formats[argIndex];
+ return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
+ }) ;
+ },
+
+ /**
+ Splits the string into words, separated by spaces. Empty strings are
+ removed from the results.
+
+ @returns {Array} An array of non-empty strings
+ */
+ w: function(str) {
+ var ary = [], ary2 = str.split(' '), len = ary2.length, string, idx=0;
+ for (idx=0; idx<len; ++idx) {
+ string = ary2[idx] ;
+ if (string.length !== 0) ary.push(string) ; // skip empty strings
+ }
+ return ary ;
+ }
+};
@wagenet
Copy link

wagenet commented Apr 12, 2011

Does this need any added sc_requires?

@ColinCampbell
Copy link
Author

Not that I saw. I did come across a case with Array when rearranging some of that which required me to include ext/function, but outside of that it should be alright. The prototype extensions are requiring the system/ base classes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment