Skip to content

Instantly share code, notes, and snippets.

@ericf
Created January 15, 2014 22:25
Show Gist options
  • Save ericf/8445982 to your computer and use it in GitHub Desktop.
Save ericf/8445982 to your computer and use it in GitHub Desktop.
'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