Created
January 15, 2014 22:25
-
-
Save ericf/8445982 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var serialize = require('./serialize'); | |
module.exports = Exposed; | |
function Exposed() { | |
Object.defineProperties(this, { | |
// Brand with constructor. | |
'@exposed': {value: Exposed}, | |
// Defines a "hidden" property that holds an ordered list of exposed | |
// namespaces. When new namespaces are exposed, existing ones are | |
// examined and removed if they would end up being noops. | |
__namespaces__: {value: []}, | |
// Defines a "hidden" property that stores serializations of data by | |
// namespace that was exposed and deemed cacheable; e.g. won't be | |
// changing. This allows the `toString()` method to run *much* faster. | |
__serialized__: {value: {}} | |
}); | |
} | |
Exposed.create = function (exposed) { | |
if (!Exposed.isExposed(exposed)) { | |
return new Exposed(); | |
} | |
// Creates a new exposed object with the specified `exposed` instance as its | |
// prototype. This allows the new object to inherit from, *and* shadow the | |
// existing object. The namespaces are copy from parent to child, and a | |
// prototype relationship is also setup for the serialized cached state. | |
return Object.create(exposed, { | |
__namespaces__: {value: []}, | |
__serialized__: {value: Object.create(exposed.__serialized__)} | |
}); | |
}; | |
Exposed.isExposed = function (obj) { | |
return !!(obj && obj['@exposed']); | |
}; | |
Exposed._getOldNamespaces = function () {}; | |
// TODO: Should this be a static method so it doesn't reserve the "add" | |
// namespace on all Exposed instances? | |
Exposed.prototype.add = function (namespace, value, options) { | |
var nsRegex = new RegExp('^' + namespace + '(?:$|\\..+)'), | |
namespaces = this.__namespaces__, | |
oldNamespaces = namespaces.filter(nsRegex.test.bind(nsRegex)), | |
serialized = this.__serialized__; | |
// Removes previously exposed namespaces, values, and serialized state which | |
// no longer apply and have become noops. | |
oldNamespaces.forEach(function (namespace) { | |
namespaces.splice(namespaces.indexOf(namespace), 1); | |
delete serialized[namespace]; | |
delete this[namespace]; | |
}, this); | |
// Stores the new exposed namespace and its current value. | |
namespaces.push(namespace); | |
this[namespace] = value; | |
// When it's deemed safe to cache the serialized form of the `value` becuase | |
// it won't change, run the serialization process once, egarly. The result | |
// is cached to greatly optimize to speed of the `toString()` method. | |
if (options && options.cache) { | |
serialized[namespace] = serialize(value); | |
} | |
}; | |
Exposed.prototype.toString = function () { | |
var rendered = {}, | |
namespaces = '', | |
data = '', | |
serialized = this.__serialized__; | |
// Values are exposed at their namespace in the order they were `add()`ed. | |
this._getApplicableNamespaces().forEach(function (namespace) { | |
var parts = namespace.split('.'), | |
leafPart = parts.pop(), | |
nsPart = 'root'; | |
// Renders the JavaScript to instantiate each namespace as needed, and | |
// does so efficiently making sure to only instantiate each part of the | |
// namespace once. | |
while (parts.length) { | |
nsPart += '.' + parts.shift(); | |
if (!rendered[nsPart]) { | |
namespaces += nsPart + ' || (' + nsPart + ' = {});\n'; | |
rendered[nsPart] = true; | |
} | |
} | |
// Renders the JavaScript to assign the serialized value (either cached | |
// or created now) to the namespace. These assignments are done in the | |
// order in which they were exposed via the `add()` method. | |
data += (nsPart + '.' + leafPart) + ' = ' + | |
(serialized[namespace] || serialize(this[namespace])) + ';\n'; | |
}, this); | |
return ( | |
'\n(function (root) {\n' + | |
'// -- Namespaces --\n' + | |
namespaces + | |
'\n// -- Data --\n' + | |
data + | |
'}(this));\n'); | |
}; | |
Exposed.prototype._getApplicableNamespaces = function () { | |
var namespaces = this.__namespaces__.concat(), | |
proto = Object.getPrototypeOf(this); | |
function isApplicable(namespace) { | |
return !namespaces.some(function (ns) { | |
var nsRegex = new RegExp('^' + ns + '(?:$|\\..+)'); | |
return nsRegex.test(namespace); | |
}); | |
} | |
while (Exposed.isExposed(proto)) { | |
namespaces.unshift.apply(namespaces, | |
proto.__namespaces__.filter(isApplicable)); | |
proto = Object.getPrototypeOf(proto); | |
} | |
return namespaces; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment