Skip to content

Instantly share code, notes, and snippets.

@apiv
Last active January 30, 2016 15:32
Show Gist options
  • Save apiv/91cf11349452395cf228 to your computer and use it in GitHub Desktop.
Save apiv/91cf11349452395cf228 to your computer and use it in GitHub Desktop.
/** Decorator Helper **/
/**
* Allows you to do either:
* @property
* test = {}
* OR
* @property()
* test = {}
* OR
* @property({option1: true})
* test = {}
*
* It tacks on options to the descriptor object
*/
export function decorator(fn) {
return function (options) {
if (arguments.length <= 1) {
return function (target, key, descriptor) {
Object.assign(descriptor, options);
return fn.call(target, target, key, descriptor);
}
} else if (arguments.length === 3) {
return fn.apply(arguments[0], arguments);
} else {
throw 'Illegal invocation of decorator';
}
}
}

I'll expand this readme soon... but, you get the point, right?

decorator helper - Returns a function that allows you to invoke the decorator function to pass options to before applying the decorator to the property/method.

type option in property decorator - Will coerce the html attribute's value to the type specified when setting from an attribute.

Initializer support - Supports ES7 initial values.

import decorator from './decorator';
export const nullable = decorator(function (target, key, descriptor) {
descriptor.nullable = true;
return descriptor;
});
import decorator from './decorator';
/** Property Decorator **/
export const property = decorator(function (target, key, descriptor) {
ensurePrototypeProperties(target);
// apply defaults
descriptor = Object.assign({
nullable: false,
attr: true,
type: 'string'
}, descriptor);
// this ensures that ALL properties have an initializer, defaulting
// to an initializer which returns undefined.
// this is so all dev-defined properties will exist in the _properties hash
// upon instantiation.
target._initializers[key] = descriptor.initializer || function () {
return undefined
};
descriptor.configurable = false;
descriptor.enumerable = true;
delete descriptor.value;
delete descriptor.initializer;
delete descriptor.writable;
descriptor.get = descriptor.get || function () {
let properties = ensureInstanceProperties(this);
return properties[key];
};
descriptor.set = descriptor.set || function (value) {
let properties = ensureInstanceProperties(this);
let oldValue = properties[key];
properties[key] = value;
this.propertyChangedCallback(key, oldValue, value);
};
if (descriptor.nullable === true) {
let getter = descriptor.get;
descriptor.get = function () {
return this::getter() || null;
}
}
if (descriptor.type) {
let setter = descriptor.set;
descriptor.set = function (value) {
value = coerce(value, descriptor.type);
return this::setter(value);
}
let getter = descriptor.get;
descriptor.get = function () {
return coerce(this::getter(), descriptor.type);
}
}
return descriptor;
});
/** Helper Methods **/
function ensurePrototypeProperties(target) {
if (!target._properties) {
// create a new WeakMap on the prototype to store
// instance => propertiesObject
Object.defineProperty(target, '_properties', {
value: new WeakMap,
enumerable: false,
writable: false,
configurable: false
});
}
if (!target._initializers) {
Object.defineProperty(target, '_initializers', {
value: {},
enumerable: false,
writable: false,
configurable: false
});
}
}
function ensureInstanceProperties(instance) {
var properties = instance._properties.get(instance);
// ensure that the properties hash exists
if (!properties) {
properties = {};
// apply initial values
for (var [prop, initializer] of Object.entries(this._initializers)) {
properties[prop] = initializer();
}
instance._properties.set(instance, properties);
}
return properties;
}
function coerce(value, type) {
switch (type) {
case 'object':
if (typeof value === 'string' && value !== '') {
value = JSON.parse(value);
}
case 'integer':
if (typeof value === 'string') {
value = parseInt(value);
}
case 'float':
if (typeof value === 'string') {
value = parseFloat(value);
}
}
return value;
}
/** Class Annotation **/
export function syncattributes(Constructor) {
var oldCreatedCallback = Constructor.prototype.createdCallback;
Constructor.prototype.createdCallback = function () {
// set properties from the attributes
Object.assign(this.attributes, this.properties);
return (typeof oldCreatedCallback === 'function') && this::oldCreatedCallback(...args);
};
var oldAttributeChangedCallback = Constructor.prototype.attributeChangedCallback;
Constructor.prototype.attributeChangedCallback = function (attr, oldValue, newValue) {
this.properties[attr] = newValue;
return (typeof oldAttributeChangedCallback === 'function') && this::oldAttributeChangedCallback(...args);
};
return Constructor;
}
/** Test **/
class Test {
@property test = {};
@property() something;
@property({type: 'float'}) value = 0.01;
propertyChangedCallback(prop, oldValue, newValue) {
console.log(prop, oldValue, newValue);
}
}
var test = new Test();
var test2 = new Test();
var test3 = new Test();
test.value = '1';
test.value = '3.02';
test.test = {a: 'a'};
test2.value = '2';
test3.value = '3';
test3.test = '{"a": "true"}';
console.log(test, test2, test3);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment