Last active
November 28, 2019 01:31
-
-
Save blikblum/91686617f4c5b03f7a44a9ba9eafb7bd to your computer and use it in GitHub Desktop.
Decorator implementations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// helper function | |
const registerDelegatedEvent = (ctor, eventName, selector, listener) => { | |
const classEvents = ctor.__events || (ctor.__events = []); | |
classEvents.push({ eventName, selector, listener }); | |
}; | |
// old dynamic spec | |
const event = (eventName, selector) => (protoOrDescriptor, methodName, propertyDescriptor) => { | |
if (typeof methodName !== 'string') { | |
const { kind, key, placement, descriptor, initializer } = protoOrDescriptor; | |
return { | |
kind, | |
placement, | |
descriptor, | |
initializer, | |
key, | |
finisher(ctor) { | |
registerDelegatedEvent(ctor, eventName, selector, descriptor.value); | |
return ensureViewClass(ctor); | |
} | |
}; | |
} | |
// legacy decorator spec | |
registerDelegatedEvent( | |
protoOrDescriptor.constructor, | |
eventName, | |
selector, | |
propertyDescriptor.value | |
); | |
}; | |
// static spec | |
decorator @event(eventName, selector) { | |
@initialize((instance, name, value) => delegate(selector ? this.renderRoot || this : this, eventName, selector, value, this);) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// helper function | |
const registerStateProperty = (ctor, name, key, options = {}) => { | |
const classStates = ctor.__states || (ctor.__states = new Set()); | |
classStates.add(name); | |
const desc = { | |
get() { | |
return this[key]; | |
}, | |
set(value) { | |
const oldValue = this[key]; | |
if (value === oldValue) return; | |
if (options.copy) { | |
if (oldValue instanceof Model) { | |
oldValue.assign(value); | |
return; | |
} else if (isState(value)) { | |
value = value.clone(); | |
} | |
} | |
if (this.isConnected) { | |
bindViewState(this, value); | |
} | |
if (isState(oldValue)) { | |
this.stopListening(oldValue); | |
} | |
this[key] = value; | |
this.requestUpdate(name, oldValue); | |
}, | |
configurable: true, | |
enumerable: true | |
}; | |
Object.defineProperty(ctor.prototype, name, desc); | |
if (ctor.createProperty) { | |
ctor.createProperty(name, { type: Object, noAccessor: true }); | |
} | |
}; | |
// old dynamic spec | |
// Method decorator to define an observable model/collection to a property | |
const state = (optionsOrProtoOrDescriptor, fieldName, options) => { | |
const isLegacy = typeof fieldName === 'string'; | |
if (!isLegacy && typeof optionsOrProtoOrDescriptor.kind !== 'string') { | |
// passed options | |
return function(protoOrDescriptor) { | |
return state(protoOrDescriptor, fieldName, optionsOrProtoOrDescriptor); | |
}; | |
} | |
const name = isLegacy ? fieldName : optionsOrProtoOrDescriptor.key; | |
const key = typeof name === 'symbol' ? Symbol() : `__${name}`; | |
if (!isLegacy) { | |
const { kind, placement, descriptor, initializer } = optionsOrProtoOrDescriptor; | |
return { | |
kind, | |
placement, | |
descriptor, | |
initializer, | |
key, | |
finisher(ctor) { | |
registerStateProperty(ctor, name, key, options); | |
return ensureViewClass(ctor); | |
} | |
}; | |
} | |
registerStateProperty(optionsOrProtoOrDescriptor.constructor, name, key, options); | |
}; | |
// static spec | |
// there's no way to change/decorate the class in a property/method decorator as in previous spec | |
// to be used the class needs to be decorated with @view | |
decorator @state(options) { | |
@initialize((instance, name, value) => instance[`__internal_${name}`] = value) | |
@register((target, name) => { | |
registerStateProperty(target.constructor, name, `__internal_${name}`, options); | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// helper function | |
const ensureViewClass = ElementClass => { | |
return class extends ElementClass { | |
constructor() { | |
super(); | |
const events = this.constructor.__events; | |
if (events) { | |
events.forEach(({ eventName, selector, listener }) => { | |
delegate(selector ? this.renderRoot || this : this, eventName, selector, listener, this); | |
}); | |
} | |
} | |
connectedCallback() { | |
super.connectedCallback && super.connectedCallback(); | |
const states = this.constructor.__states; | |
if (states) { | |
states.forEach(name => { | |
bindViewState(this, this[name]); | |
}); | |
} | |
} | |
disconnectedCallback() { | |
this.stopListening(); | |
super.disconnectedCallback && super.disconnectedCallback(); | |
} | |
}; | |
}; | |
// old dynamic spec | |
const view = classOrDescriptor => { | |
if (typeof classOrDescriptor === 'object') { | |
const { kind, elements } = classOrDescriptor; | |
return { | |
kind, | |
elements, | |
finisher: ensureViewClass | |
}; | |
} | |
return ensureViewClass(classOrDescriptor); | |
}; | |
// static spec | |
decorator @view { | |
@wrap(klass => { | |
return ensureViewClass(klass); | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// helpers | |
const ensureVirtualClass = ElementClass => { | |
if (ElementClass[isClassDecorated]) return ElementClass; | |
ElementClass[isClassDecorated] = true; | |
const VirtualClass = class extends ElementClass { | |
connectedCallback() { | |
super.connectedCallback && super.connectedCallback(); | |
const virtualStates = this.constructor.__virtualStates; | |
if (virtualStates) { | |
virtualStates.forEach(name => { | |
const virtualCollection = this[name]; | |
if (virtualCollection) { | |
if (!virtualCollection.parent) { | |
virtualCollection.parent = parentMap.get(virtualCollection); | |
} | |
bindVirtualCollection(this, virtualCollection); | |
} | |
}); | |
} | |
} | |
disconnectedCallback() { | |
const virtualStates = this.constructor.__virtualStates; | |
if (virtualStates) { | |
virtualStates.forEach(name => { | |
const virtualCollection = this[name]; | |
if (virtualCollection) { | |
parentMap.set(virtualCollection, virtualCollection.parent); | |
virtualCollection.parent = null; | |
this.stopListening(virtualCollection); | |
} | |
}); | |
} | |
super.disconnectedCallback && super.disconnectedCallback(); | |
} | |
}; | |
Events.extend(VirtualClass.prototype); | |
return VirtualClass; | |
}; | |
const registerVirtualState = (ctor, name, key, options = {}) => { | |
const virtualStates = ctor.__virtualStates || (ctor.__virtualStates = new Set()); | |
virtualStates.add(name); | |
const { parent } = options; | |
if (parent) { | |
const parentKey = typeof parent === 'symbol' ? Symbol() : `__vcParent_${name}`; | |
const superDesc = Object.getOwnPropertyDescriptor(ctor.prototype, parent); | |
const parentDesc = { | |
get() { | |
return this[parentKey]; | |
}, | |
set(value) { | |
if (superDesc && superDesc.set) { | |
superDesc.set.call(this, value); | |
} | |
setParentCollection(this, value, name, key, options); | |
this[parentKey] = value; | |
}, | |
configurable: true, | |
enumerable: true | |
}; | |
Object.defineProperty(ctor.prototype, parent, parentDesc); | |
} | |
const desc = { | |
get() { | |
return this[key]; | |
}, | |
set(value) { | |
setParentCollection(this, value, name, key, options); | |
}, | |
configurable: true, | |
enumerable: true | |
}; | |
Object.defineProperty(ctor.prototype, name, desc); | |
if (ctor.createProperty) { | |
ctor.createProperty(name, { type: Object, noAccessor: true }); | |
} | |
}; | |
// old dynamic spec | |
const virtualState = (optionsOrProtoOrDescriptor, fieldName, options) => { | |
const isLegacy = typeof fieldName === 'string'; | |
if (!isLegacy && typeof optionsOrProtoOrDescriptor.kind !== 'string') { | |
// passed options | |
return function(protoOrDescriptor) { | |
return virtualState(protoOrDescriptor, fieldName, optionsOrProtoOrDescriptor); | |
}; | |
} | |
const name = isLegacy ? fieldName : optionsOrProtoOrDescriptor.key; | |
const key = typeof name === 'symbol' ? Symbol() : `__${name}`; | |
if (!isLegacy) { | |
const { kind, placement, descriptor, initializer } = optionsOrProtoOrDescriptor; | |
return { | |
kind, | |
placement, | |
descriptor, | |
initializer, | |
key, | |
finisher(ctor) { | |
const result = ensureVirtualClass(ctor); | |
registerVirtualState(result, name, key, options); | |
return result; | |
} | |
}; | |
} | |
registerVirtualState(optionsOrProtoOrDescriptor.constructor, name, key, options); | |
}; | |
// static spec | |
// no way to change the class. | |
// to make it work needs to decorate to class itself | |
decorator @virtualState(options) { | |
@register((target, name) => { | |
registerVirtualState(target.constructor, name, `__internal_${name}`, options); | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment