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; } }