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