-
-
Save pottedmeat/acce43a156e989429ae5 to your computer and use it in GitHub Desktop.
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
function rebase(fn: Function): Function { | |
return function(...args: any[]) { | |
return fn.apply(this, [ this ].concat(args)); | |
}; | |
} | |
const doExtend = rebase(extend); | |
const doMixin = rebase(mixin); | |
const doOverlay = rebase(overlay); | |
function stamp(base: any): void { | |
base.extend = doExtend; | |
base.mixin = doMixin; | |
base.overlay = doOverlay; | |
} | |
interface GenericClass<T> { | |
new (...args: any[]): T; | |
} | |
interface ComposerClass<O, T> { | |
new (options?: O): T; | |
extend<U>(extension: U): ComposerClass<O, T&U>; | |
mixin<P, U>(mixin: ComposerClass<P, U>): ComposerClass<O&P, T&U>; | |
mixin<P, U>(mixin: GenericClass<U>): ComposerClass<O, T&U>; | |
overlay(overlay: (proto: T) => void): ComposerClass<O, T>; | |
} | |
interface ComposerFunction<O> { | |
(options?: O): void; | |
} | |
interface Composer { | |
<O, A>(superClass: GenericClass<A>, initFunction?: ComposerFunction<O>): ComposerClass<O, A>; | |
<O, A, P>(superClass: ComposerClass<O, A>, initFunction?: ComposerFunction<P>): ComposerClass<O&P, A>; | |
<O, A>(superClass: A, initFunction?: ComposerFunction<O>): ComposerClass<O, A>; | |
create<O, A>(superClass: GenericClass<A>, initFunction?: ComposerFunction<O>): ComposerClass<O, A>; | |
create<O, A, P>(superClass: ComposerClass<O, A>, initFunction?: ComposerFunction<P>): ComposerClass<O&P, A>; | |
create<O, A>(superClass: A, initFunction?: ComposerFunction<O>): ComposerClass<O, A>; | |
extend<O, A, B>(base: ComposerClass<O, A>, extension: B): ComposerClass<O, A&B>; | |
mixin<O, P, A, B>(base: ComposerClass<O, A>, mixin: ComposerClass<P, B>): ComposerClass<O&P, A&B>; | |
mixin<O, A, B>(base: ComposerClass<O, A>, mixin: GenericClass<B>): ComposerClass<O, A&B>; | |
overlay<O, A>(base: ComposerClass<O, A>, overlay: (proto: A) => void): ComposerClass<O, A>; | |
} | |
function extend<O, A, B>(base: ComposerClass<O, A>, extension: B): ComposerClass<O, A&B>; | |
function extend<O>(base: ComposerClass<O, any>, extension: any): ComposerClass<O, any> { | |
Object.keys(extension).forEach(key => base.prototype[key] = extension[key]); | |
return base; | |
} | |
function mixin<O, P, A, B>(base: ComposerClass<O, A>, mixin: ComposerClass<P, B>): ComposerClass<O&P, A&B>; | |
function mixin<O, A, B>(base: ComposerClass<O, A>, mixin: GenericClass<B>): ComposerClass<O, A&B>; | |
function mixin<O>(base: ComposerClass<O, any>, mixin: any): ComposerClass<O, any> { | |
Object.keys(mixin.prototype).forEach(key => base.prototype[key] = mixin.prototype[key]); | |
return base; | |
} | |
function overlay<O, A>(base: ComposerClass<O, A>, overlay: (proto: A) => void): ComposerClass<O, A> { | |
overlay(base.prototype); | |
return base; | |
} | |
/* This is a custom type guard, it will narrow the type so you don't have to the cast */ | |
function isFunction(arg: any): arg is Function { | |
return typeof arg === 'function'; | |
} | |
function create<O, A>(base: GenericClass<A>, initFunction?: ComposerFunction<O>): ComposerClass<O, A>; | |
function create<O, A, P>(base: ComposerClass<O, A>, initFunction?: ComposerFunction<P>): ComposerClass<O&P, A>; | |
function create<O, A>(base: A, initFunction?: ComposerFunction<O>): ComposerClass<O, A>; | |
function create<O>(base: any, initFunction?: ComposerFunction<O>): any { | |
function Creator(...args: any[]): void { | |
if (isFunction(initFunction)) { | |
initFunction.apply(this, args); | |
} | |
} | |
Creator.prototype = (typeof base === 'function' ? Object.create(base.prototype) : base); | |
stamp(Creator); | |
return Creator; | |
} | |
stamp(create); | |
(<Composer> create).create = create; | |
let compose: Composer = <Composer> create; | |
class Widget { | |
buildRendering() { | |
console.log('Widget.buildRendering'); | |
} | |
} | |
class Templated { | |
render() { | |
console.log('Templated.render'); | |
} | |
} | |
interface Serializable { | |
toJson() | |
} | |
const MyWidget = compose(Widget, function(){ | |
console.log('MyWidget.constructor'); | |
}) | |
.mixin(Templated) | |
.overlay(function (prototype) { | |
prototype.buildRendering = function() { | |
console.log('MyWidget.buildRendering'); | |
this.render(); | |
}; | |
}) | |
.extend(<Serializable>{ | |
toJson() { | |
return '{"foo":"bar"}'; | |
} | |
}); | |
const widget = new MyWidget(); | |
widget.buildRendering(); | |
console.log(widget.toJson()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment