Skip to content

Instantly share code, notes, and snippets.

@kitsonk
Forked from pottedmeat/chained_compose.ts
Last active October 6, 2015 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kitsonk/5c16934888547073c530 to your computer and use it in GitHub Desktop.
Save kitsonk/5c16934888547073c530 to your computer and use it in GitHub Desktop.
/* ports from lib.es6.d.ts, should come from dojo/core::WeakMap */
interface Symbol {
toString(): string;
valueOf(): Object;
[Symbol.toStringTag]: string;
}
interface SymbolConstructor {
prototype: Symbol;
(description?: string|number): symbol;
for(key: string): symbol;
keyFor(sym: symbol): string;
hasInstance: symbol;
isConcatSpreadable: symbol;
iterator: symbol;
match: symbol;
replace: symbol;
search: symbol;
species: symbol;
split: symbol;
toPrimitive: symbol;
toStringTag: symbol;
unscopables: symbol;
}
declare var Symbol: SymbolConstructor;
interface IteratorResult<T> {
done: boolean;
value?: T;
}
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface WeakMap<K, V> {
clear(): void;
delete(key: K): boolean;
get(key: K): V;
has(key: K): boolean;
set(key: K, value?: V): WeakMap<K, V>;
[Symbol.toStringTag]: string;
}
interface WeakMapConstructor {
new (): WeakMap<any, any>;
new <K, V>(): WeakMap<K, V>;
new <K, V>(iterable: Iterable<[K, V]>): WeakMap<K, V>;
prototype: WeakMap<any, any>;
}
declare var WeakMap: WeakMapConstructor;
/* should come from dojo/core::lang */
interface ObjectConstructor {
assign<T, U>(target: T, source: U): T & U;
assign<T, U, V>(target: T, source1: U, source2: V): T & U & V;
assign<T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W;
assign(target: any, ...sources: any[]): any;
}
/* Used for mapping the init functions */
const initFnMap = new WeakMap<Function, Function[]>();
/**
* We need to rebase some functions where the first argument is scoped to be
* this. .bind() ends up creating a lot of new functions and actually we want
* to keep it simple and efficient.
* @param {Function} fn The function be rebased
* @return {Function} The rebased function
*/
function rebase(fn: Function): Function {
return function(...args: any[]) {
return fn.apply(this, [ this ].concat(args));
};
}
/* These rabased functions are used to decorate the constructors */
const doExtend = rebase(extend);
const doMixin = rebase(mixin);
const doOverlay = rebase(overlay);
/**
* Apply the rebased functions
* @param {any} base The item to be decorated
*/
function stamp(base: any): void {
base.extend = doExtend;
base.mixin = doMixin;
base.overlay = doOverlay;
}
/**
* Generic Class Constructor API with a Generic for type inference
*/
interface GenericClass<T> {
new (...args: any[]): T;
}
/**
* Compose constructor API
*/
interface ComposerClass<O, T> {
new (options?: O): T;
/* Extensions are objects who's enumerable own properties will be mixed
* into the class */
extend<U>(extension: U): ComposerClass<O, T&U>;
/* Either compose constructors or generic constructors cann be mixed into
* the classes */
mixin<P, U>(mixin: ComposerClass<P, U>): ComposerClass<O&P, T&U>;
mixin<P, U>(mixin: GenericClass<U>): ComposerClass<O, T&U>;
/* Overlay functions will be passed the prototype which the can modify
* decorate */
overlay(overlay: (proto: T) => void): ComposerClass<O, T>;
}
/**
* Compose initialisation function API
*/
interface ComposerFunction<O> {
(options?: O): void;
}
/**
* The composistion library API
*/
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>;
}
/**
* Helper function that returns a new function and a new reference to a prototype
*/
function cloneCreator<O, T>(base?: ComposerClass<O, T>): ComposerClass<O, T>;
function cloneCreator(base?: any): any {
function Creator(...args: any[]): any {
const initFns = initFnMap.get(this.constructor);
if (initFns) {
initFns.forEach(fn => fn.apply(this, args));
}
}
if (base) {
Object.assign(Creator.prototype, base.prototype);
initFnMap.set(Creator, [].concat(initFnMap.get(base)));
}
else {
initFnMap.set(Creator, []);
}
Creator.prototype.constructor = Creator;
stamp(Creator);
return Creator;
}
/**
* Extend a compose constructor
*/
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> {
base = cloneCreator(base);
Object.keys(extension).forEach(key => base.prototype[key] = extension[key]);
return base;
}
/**
* Mixin to a compose constructor
*/
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> {
base = cloneCreator(base);
Object.keys(mixin.prototype).forEach(key => base.prototype[key] = mixin.prototype[key]);
return base;
}
/**
* Allow a function to overlay the prototype of a composer class
*/
function overlay<O, A>(base: ComposerClass<O, A>, overlay: (proto: A) => void): ComposerClass<O, A> {
base = cloneCreator(base);
overlay(base.prototype);
return base;
}
/**
* Create a new compose constructor based on a generic constructor, a composer constructor
* or just a "prototype" object.
*/
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 {
const Creator = cloneCreator();
if (initFunction) {
initFnMap.get(Creator).push(initFunction);
}
/* mixin the base into the prototype */
Object.assign(Creator.prototype, typeof base === 'function' ? base.prototype : base);
/* return the new constructor */
return Creator;
}
/* decorating the compose function */
(<Composer> create).create = create;
(<Composer> create).extend = extend;
(<Composer> create).mixin = mixin;
(<Composer> create).overlay = overlay;
/* Let's coerce it to the API */
const compose: Composer = <Composer> create;
/**
* Now some examples for the use cases we are eventually looking at
*/
class Widget {
buildRendering() {
console.log('Widget.buildRendering');
}
}
class Templated {
render() {
console.log('Templated.render');
}
}
const MyWidget = compose(Widget, function(){
console.log('MyWidget.constructor');
})
.mixin(Templated)
.overlay(function (prototype) {
prototype.buildRendering = function() {
console.log('MyWidget.buildRendering');
this.render();
};
});
const MyFooWidget = MyWidget.extend({
foo: 'foo'
});
const widget = new MyWidget();
widget.buildRendering();
@kitsonk
Copy link
Author

kitsonk commented Sep 23, 2015

Here is a TypeScript Playground for this.

@sebilasse
Copy link

👍
Hey,
nice - thanks - @kitsonk just one note:
there might be IOC cases where someone wants to .mixin multiple classes based on runtime conditions / dependencies. Therefore it might be super useful if .mixin would support arrays of classes as well (to inject them as a variable).
E.g. this playground (also demos what you commented in microsoft/TypeScript#3694 ;)

// compose ...
   .mixin(OtherWidget)
   .mixin(Templated)

could also be this

const myMixins = [OtherWidget, Templated];
// compose ...
   .mixin(myMixins)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment