avocado-old/packages/mixins/property.js
2019-04-11 17:53:38 -05:00

165 lines
4.4 KiB
JavaScript

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);
// Set default.
if (undefined !== metaDefault) {
if (meta.set) {
meta.set.call(this, metaDefault);
}
else {
this[transformedProperty] = metaDefault;
}
}
}
}
// Getter.
const getter = meta.get ? meta.get : 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 = 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 = 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 = function(value) {
const old = this[key];
this[assignMethod](value);
if (old !== value) {
this[emitMethod](`${key}Changed`, old, value);
}
};
}
else {
// Set with emitter.
setter = 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 = function(value) {
this[assignMethod](value);
};
}
else {
// Set raw.
setter = function(value) {
this[transformedProperty] = value;
};
}
}
Object.defineProperty(Property.prototype, key, {
enumerable: true,
get: getter,
set: setter,
});
return Property;
}
}