Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
DataHolder is used to manage model objects with knockout observable values.
# DataHolder handles most Knockout binding situations for properties
# transparently. You can instantiate a DataHolder with a set of attributes
# that are turned into observable properties of the DataHolder.
# Attributes passed in that are functions are not wrapped in an observable.
# Additionally, all attributes whose names end in `Fn' are ensured to be
# wrapped in functions and set as is, without an observable.
# All attributes are passed through the preprocessAttribute function, which
# can be overridden in child classes. These can be used to do type
# conversions and data lookups, for example, as well as, for example,
# wrapping arrays in observableArray instead of observable.
# Incoming attribute values that are already observables will be assigned
# directly.
# You can call updateAttributesWith on a DataHolder instance at any time and
# pass in new attributes to update the properties of the DataHolder. This
# will respect existing observables, setting their values where needed by
# invoking them correctly and triggerin their listeners. It will also unwrap
# incoming observables where applicable.
# When instantiating a DataHolder, you can also pass it an object of
# defaults. This is typically used when invoking the DataHolder constructor
# as a super-constructor. Last but not least, you can pass in a list of
# attributes that should be explicitly set to undefined if not present in
# the list of initial attributes. These will be wrapped in observables whose
# values will be undefined, and will thus ensure that no errors are produced
# regarding undefined properties.
class DataHolder
constructor: (attributes, defaults, defaultUndefineds) ->
defaults ||= {}
defaultUndefineds ||= []
defaults[attribute] = undefined for attribute in defaultUndefineds
attributes[prop] = value for prop, value of defaults when not attributes[prop]
@updateAttributesWith attributes
setValue: (attribute, value) ->
# Don't replace existing values with undefined.
return if typeof value == 'undefined' && this[attribute]
current = this[attribute]
if typeof value == 'function' && value['__ko_proto__'] &&
typeof current == 'function' && current['__ko_proto__']
current value()
else if typeof current == 'function' && current['__ko_proto__']
current value
else if typeof value == 'function'
this[attribute] = value
this[attribute] = ko.observable value
preprocessAttribute: (attribute, value) ->
if attribute.match(/Fn$/) && typeof value != 'function' && value
os.processFnString value
updateAttributesWith: (attributes) ->
for attribute of attributes
@setValue attribute, @preprocessAttribute(attribute, attributes[attribute])
# Expose globally.
os.DataHolder = DataHolder
Copy link

farmdawgnation commented May 2, 2016

@Shadowfiend Any chance of getting an updated version of this sometime soon? ;)

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