Skip to content

Instantly share code, notes, and snippets.

@trek
Created June 28, 2011 17:18
Show Gist options
  • Save trek/1051646 to your computer and use it in GitHub Desktop.
Save trek/1051646 to your computer and use it in GitHub Desktop.
sproutcore-metal
/core.js
defines the SC namespace
/platform.js
defines object creation
defines defineProperty
checks for native setters/getters
/utils.js
s: guidFor
defines SC.meta which will give you information about a property on an object
define some typical functional-style fns:
wrap() (mostly used for faking _super), isArray(), makeArray() (used for transforming arguments into arrays)
/events.js
Code (i.e not "User") events. Defines
SC.addListener
SC.removeListener
SC.sendEvent
SC.hasListeners
SC.watchedEvents
SC.listenersFor
Kind of like _.js or Backbone custom events. Objects will be notified of property change events
on other objects
Events added, removed, and triggered will have names like
length:before
firstObject:before
lastObject:before
element:before
parentView.content:before
content.title:before
length:change
firstObject:change
lastObject:change
element:change
parentView.content:change
content.title:change
/accessors.js
_Object_ related differences in browser (you're probably familiar with DOM related normalizing).
Provides single interface for good techniques, delegating to built-in (i.e. FASTER) browser features on
modern browsers, falls back to hand-made (i.e. SLOWER) implementations for older browsers.
jquery, underscore, prototype
ruby's active-support
great way to get modern browser features into older browsers
adds get/set behavior
eventually these end up as SC.get and SC.set
SC.get does direct property access. If the property is undefined it will call obj.unknownProperty(key),
otherwise returns undefined
SC.getPath
takes a path ("string representing object space path"), walks that path, and resolves to an object
SC.setPath
uses helpers
SC.normalizePath
SC.normalizeTuple
/properties.js
SC has some special kinds of property behavior related to setting/getting
that it needs to build complex behavior later on.
What's an SC.Descriptor? "sets of behaviors on set/get property access"
lowest level is identical to direct property access
If a property on an object being "watched" by other objects, you must access it with get/set.
In development SC will warn when you attempt direct access on a property that should be guarded in this way.
Mimics native property behavior and Object.create with this behavior. Why? direct property access seems grand
in theory but presents a major barrier to building up layers of behavior though inheritance or components.
SC.create builds on SC.platform.create to make properties a part of the behavior.
Also adds SC.createPrototype for making objects suitable for setting as prototypes but which themselves are
not intended for use other than later inheritance.
/watching.js
Watching is an internal library you're not likely to use yourself, but key to understanding later
functionality of SC. It adds a custom Descriptor `SC.SIMPLE_PROPERTY.watched` to objects
This Descriptor (defined in metal/properties) augments `set`ting calls so that they invoke
SC's property change interface:
if (watching) SC.propertyWillChange(obj, keyName);
m.values[keyName] = value;
if (watching) SC.propertyDidChange(obj, keyName);
SC.propertyWillChange and SC.propertyDidChange are both internal implementation tools. You'll likely not call them
SC.propertyWillChange and SC.propertyDidChange wrap calls to SC.notifyBeforeObservers(obj, keyName) and
SC.notifyObservers(obj, keyName) which are key parts of the next part of the the library: Observers.
/observer.js
talk about observer pattern to sound smart.
uses the accessor behavior to implement property notification. Includes some tricks for only sending messages
once to any object that needs to receive one. Keeps an observer queue.
You won't be calling `SC.addObserver`, but its the heart of the property observation system. Internally it
uses `SC.addListener` which is part of the SC event system defined in metal/events. Although events is
a general eventing system like those found in other js browser app frameworks, it's only used
You could use it to solve problems the same way something like X or Y's event system, but you'll see later
that higher levels of the SC stack obviate the need.
For now, it's useful to understand a Observer as an object that registers an event listener to receive notices
of changes on a particular property of an object. Realistically the Event/Observer system could be
rolled into a single concept, but having events separate does lend itself to a more
decoupled system (better why?).
After registering an event handler for changes to a particular property, `SC.addObserver` starts the watching
process by calling `SC.watch(obj, path)`
When an object property changes via obj.set('myPropertyName', 'some new value'), it triggers the listener
passes through the augmented `set`ing behavior added by `SC.Descriptor.watching` will calls
SC.propertyWillChange and SC.propertyDidChange.
Finally, these call `SC.notifyBeforeObservers` and `SC.notifyObservers` which will notify any objects observing
for changes on these properties.
Later in the framework, this will all be used by runtime/bindings (which probably should be moved to metal lib).
/mixin
Object composition support (see the oldie-but-goodie: http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/).
A bit like `extend` in other frameworks or libraries, but with proper respect for previous augmentations (whereas
`extend` typical just overwrites).
/computed
Adds support for computed properties via `SC.computed()`. Computed properties are function defined
on an object whose value is derived from other properties of the object.
The canonical example offered is usually this a `fullName` function:
fullName: function(){
return this.get('firstName') + ' ' + this.get('lastName');
},
if either of the "dependent keys" (`firstName` or `lastName`) changes, then the return value of `fullName()` would change,
objects observing the value of `fullName` need to be notified.
You're unlikely to encounter `SC.computed()`. Later in the runtime lib `Function.prototype` is augmented with a `property()`
function that takes a list of dependent keys as an argument and proxies to this library.
...
fullName: function(){
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName'),
...
/runtime
Runtime augments core javascript types. You can consider this the sproutcore "language"
Sometimes by extending their `prototype`, sometimes by wrapping them in new SC-specific
objects and adds a handful of top-level helper functions. (can turn off with SC.EXTEND_PROTOTYPES)
If you turn off these extensions they'll still be available in SC-specific helpers.
E.g. if you prefer not to pollute `String.prototype` with `dasherize`, you'll still have access to
`SC.String.dasherize('myStringThatNeedsDasherizing')
/core
defines NO, YES. Silly holdover from Apple days.
protects from spurios console calls
SC.typeOf - typical augmented object test to make up for javascript's mostly useless native typeof
SC.none - returns true for tests of both null and undefined
SC.empty - returns true for tests of null, undefined, and empty string
SC.isArray - tests for array-ness (dup!)
SC.compare - compares objects for sorting. returns -1, 0, 1
SC.copy - copying objects
SC.inspect - string inspection
SC.isEqual - tests deeper equality
SC.keys - returns lists of keys on an object literal
SC.K - an empty function
SC.Error - wraps native JS error
SC.Logger - wraps `console`
/ext/string
adds common string manipulations to `String.prototype`
/ext/function
adds observing and computed property support to `Function.prototype`
/ext/mixin
adds support for detecting created object property names that end in `Binding` (e.g. `valueBinding`). This is used later in
runtime/system/bindings.
/mixins adds lots of useful patterns you'd find baked right into other languages:
/mixins/enumerable, /mixins/mutable_array, /mixins/mutable_enumerable, /mixins/array
Defines an enumerable interface for Array-like objects to adhere to, with lots of handy methods for working with
collections (maps, reduces, filters, finding, uniqifying, etc) as well as observer support for Array-like properties.
E.g. length could otherwise not be observed.
These are used a lot in higher-level libraries in the framework:
`SC.RecordArray = SC.Object.extend(SC.Enumerable, SC.Array, SC.MutableEnumerable, SC.MutableArray, ...`
/mixins/comparable
defines and provides support for object to object comparisons (useful for equality checks and sorting). If you want this in
some special object family you've written implement the method `compare`
/mixins/copyable
defines standard interface for copying objects. If you want this in some special object family you've
written implement the method `copy`
/mixins/freezable
defines standard interface for freezing objects (i.e. preventing further mutation of its properties).
/mixins/observable
adds support for object-level access to the metal/observing system. So, instead of using `SC.get(object, 'propName')` you
have access to `object.get('propName')`. Most importantly this is later mixed into SC's base Object (`SC.Object`) so that
nearly every object you'll be working with as a developer will have observation support.
/system/core_object
Here begins inklings of the layer you're mostly likely to interact with as a developer using SC.
SC.CoreObject provides support to its prototypal children for creating and destroying objects,
a standard object initialization process.
/system/object
Defines SC.Object which is identical to SC.CoreObject with SC.Observable mixed in to provide observation support.
This basically marks the waterline for what a typical developer using SproutCore will use. Everything I've mentioned previously
exists to augment the javascript environment to enable `SC.Object` and higher layers to properly function.
/system/string
Defines SC.String which holds useful helper methods for string manipulation (formatting, localizing, inflecting). Unless
you've specifically set SC.EXTEND_PROTOTYPES to false, these have also been added to `String.prototype` which is where
you're likely be using them.
/system/native_array
Defines SC.NativeArray mixin that bundles up most of the enumerable and array and mixins in addition to SC.Observable, SC.Copyable
and then applies them to the environment's native `Array` (unless you've, again, set SC.EXTEND_PROTOTYPES to false)
/system/set
Adds Sets (unordered collection of unique objects) to the environment. This is used by higher level libraries in the framework
although you could use it yourself if you needed Set behavior.
/system/namespace
SC.Namespace is just an descendent of SC.Object. It's intended to just be a dumb container for nesting code. It also helps
print more meaningful output to help you find where an object lives in an object hierarchy.
/system/application
SC.Application is the only object descendent of SC.Namespace in the framework. Identical to SC.Namespace. Comments indicate that
you should implement a `main` method for application initialization. However, this function will never be called by the framework
so I suspect it's vestigial or not yet ported.
/system/array_proxy
SC.ArrayProxy takes the place of what used to be ArrayController. Proxy is definitely a more suitable name. An array proxy wraps a
collection (set as its `content` property) allowing a developer to extend the behavior of collection without polluting the collection
itself.
Imagine you have a 100 customers each with their own collection of payments. As collections, each set of payments will already
have many useful methods that are shared by all collections. However, you may wish to add specific functionality to the collections
while you're working with them. One option would be to extend Array to include payment-collection specific behavior (e.g. create
a special PaymentsArray family of objects).
Another option (the one adopted by SproutCore) is to wrap the collection in a proxy while you're working with it, implement new
behavior on that proxy, and delegate all other behavior back to the original collection.
MyApp.customerPaymentsProxy = SC.ArrayProxy.create({
areOutstandingPaymentsDue: function(){
...
}
})
When you call `MyApp.customerPaymentsProxy.get('length')` the Proxy will delegate to its `content` property. When you
call `MyApp.customerPaymentsProxy.get('areOutstandingPaymentsDue') it will call your custom function.
I've seen this pattern often called a Delegate Pattern.
/system/object_proxy
An Object-specific version of array_proxy doesn't exist yet. This somewhat complicates following the master/detail pattern
that is so common in UI programming.
/system/bindings
Where so much of the sproutcore magic gets connected together. Bindings provide a methodology for synchronizing
values in your application. You'll most likely encounter bindings when you specific property names that end in "Binding".
myObject = SC.Object.create({
valueBinding: "MyApp.someCollectionOfThings.selection.title"
})
This will ensure that the `value` property of myObject will be equal to the object at the path "MyApp.someCollectionOfThings.selection.title".
So, if the collection at the path "MyApp.someCollectionOfThings" changes (which also means that the `.selected` has changed, which further means
that the `.title` has changed), the `value` of `myObject` will also be changed.
Further, if you change `value` of `myObject`, that difference will also be synchronized to the other end of the Binding
(e.g. whatever object in memory is represented by the path "MyApp.someCollectionOfThings.selection" will have its `title` property set).
Bindings include code to keep "echo"ing of change notifications from occurring. So, if "MyApp.someCollectionOfThings.selection.title"
is changed and "myObject.value" is changed to synchronize to the new value "MyApp.someCollectionOfThings.selection.title" the
change notifications stop there (rather than continually notifying each object of new changes in an infinite loop).
Bindings also have support for direct creation (as opposed to being referenced by a string like the code sample above). This will let
you customize the Binding's behavior. So, referring to a binding by a path/string (`valueBinding: "MyApp.someCollectionOfThings.selection.title"`)
gets the default behavior, but you can specify very specific functionality using the Bindings object itself:
myObject = SC.Object.create({
valueBinding: SC.Binding.SC.Binding.oneWay("MyApp.someCollectionOfThings.selection.title")
})
This tells the binding that changes anywhere along the path "MyApp.someCollectionOfThings.selection.title" should trigger synchronization of
`myObject.value` but changes to `myObject.value` will not be reflected on the other side of the binding.
Internally, Bindings work by setting themselves as intermediate observers of the objects at both sides of
the binding (using `SC.addObserver` twice). Support for automatically detecting property names that match the
/^.+Binding/ pattern is shoehorned into Objects by SC._mixinBindings defined in runtime/lib/ext/mixin.
/system/run_loop
Defines a custom RunLoop object that layers on top of the native browser run loop (which only has a few entry points and isn't terribly
customizable). You won't be interacting with this much yourself but other parts of the framework (particularly Bindings, View rendering,
and mouse/keyboard events) will use `SC.run.schedule()` to avoid funkiness with the native browser runloop/event system
A good read of why a system like this is necessary: http://ejohn.org/blog/how-javascript-timers-work/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment