avocado-old/packages/core/property.js
2019-04-28 23:45:03 -05:00

171 lines
4.6 KiB
JavaScript

/**
* This Mixin contains a lot of nasty looking unrolls and string functions.
* Trying to optimize this very hot spot!
*/
export function PropertyMixin(key, meta = {}) {
// Param check.
if (!meta || 'object' !== typeof meta) {
throw new TypeError(
`Expected meta for Property(${
key
}) to be object. ${
JSON.stringify(meta)
} given.`
);
}
return (Superclass) => {
// Sanity check.
if (Superclass.prototype[key]) {
throw new TypeError(`can't redefine Avocado property "${key}"`);
}
// Handle defaults.
let metaDefault;
if (null === meta.default) {
metaDefault = null;
}
else if (undefined === meta.default) {
metaDefault = undefined;
}
else {
metaDefault = JSON.parse(JSON.stringify(meta.default));
}
// Transform key. TODO: dynamic? Comes at a price...
let transformedProperty;
if (meta.transformProperty) {
transformedProperty = meta.transformProperty(key);
}
else {
transformedProperty = `$$avocado_property_${key}`;
}
class Property extends Superclass {
constructor(...args) {
super(...args);
// Initialize?
if (meta.initialize) {
meta.initialize.call(this);
}
else {
// Set default.
if (undefined !== metaDefault) {
this[transformedProperty] = metaDefault;
}
}
}
}
// Getter.
const getter = meta.get ? meta.get : new Function(`
return this.${transformedProperty};
`);
// Setter.
let setter;
// Helper to define assigner.
function defineAssigner() {
const assignMethod = `${transformedProperty}$assign`;
Object.defineProperty(Property.prototype, assignMethod, {
value: meta.set,
});
return assignMethod;
}
// Tracking?
if (meta.track) {
// Define emitter.
const emitter = meta.emit ? meta.emit : function(...args) {
if (this.emit) {
this.emit(...args);
}
};
const emitMethod = `${transformedProperty}$emit`;
Object.defineProperty(Property.prototype, emitMethod, {
value: emitter,
});
// Compare?
if (meta.eq) {
// Define comparator.
const comparator = meta.eq ? meta.eq : function(l, r) {
return l === r;
};
const compareMethod = `${transformedProperty}$compare`;
Object.defineProperty(Property.prototype, compareMethod, {
value: comparator,
});
// Assign?
if (meta.set) {
// Define assigner.
const assignMethod = defineAssigner();
// Set with assigner, comparator, and emitter.
setter = new Function('value', `
const old = this.${key};
this.${assignMethod}(value);
if (!this.${compareMethod}(old, value)) {
this.${emitMethod}('${key}Changed', old, value);
}
`);
}
else {
// Set with comparator and emitter.
setter = new Function('value', `
const old = this.${key};
this.${transformedProperty} = value;
if (!this.${compareMethod}(old, value)) {
this.${emitMethod}('${key}Changed', old, value);
}
`);
}
}
// No compare.
else {
// Assign?
if (meta.set) {
// Define assigner.
const assignMethod = defineAssigner();
// Set with assigner and emitter.
setter = new Function('value', `
const old = this.${key};
this.${assignMethod}(value);
if (old !== value) {
this.${emitMethod}('${key}Changed', old, value);
}
`);
}
else {
// Set with emitter.
setter = new Function('value', `
const old = this.${key};
this.${transformedProperty} = value;
if (old !== value) {
this.${emitMethod}('${key}Changed', old, value);
}
`);
}
}
}
// No tracking?
else {
// Assign?
if (meta.set) {
// Define assigner.
const assignMethod = defineAssigner();
// Set with assigner.
setter = new Function('value', `
this.${assignMethod}(value);
`);
}
else {
// Set raw.
setter = new Function('value', `
this.${transformedProperty} = value;
`);
}
}
Object.defineProperty(Property.prototype, key, {
configurable: true,
enumerable: true,
get: getter,
set: setter,
});
return Property;
}
}