Skip to content

Instantly share code, notes, and snippets.

@blikblum
Last active November 28, 2019 01:31
Show Gist options
  • Save blikblum/91686617f4c5b03f7a44a9ba9eafb7bd to your computer and use it in GitHub Desktop.
Save blikblum/91686617f4c5b03f7a44a9ba9eafb7bd to your computer and use it in GitHub Desktop.
Decorator implementations
// 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);)
}
// 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);
})
}
// 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);
})
}
// 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