How to extend static objects like Date or Object?

Topics: General
Nov 20, 2012 at 3:30 PM
Edited Nov 20, 2012 at 7:00 PM

Hi, i've try to get sugar.js working with TypeScript. I found a type definition file https://github.com/borisyankov/DefinitelyTyped/blob/master/sugar/sugar-1.3.d.ts which extends the Date interface like that.

Date.now().format();

But whats missing currently are this:

Date.create("now");

So how to extends the static objects? It seems simple to extend the interfaces. But I had no luck yet to extend the global variables.

Nov 20, 2012 at 11:33 PM
Edited Nov 20, 2012 at 11:50 PM

I think there might be a bug/issue, if you look into lib.d.ts you will see Date has some static functions declared using "declare var Date" rather than the interface.  So I tried this for sugar.d.ts, using "X" as the identifier the compiler will recognize this and X.create() is a valid function.  However using Date the typescript compiler does not recognize the declare and says Date.create() is not defined.

declare var X: {
	create(locale?: string): Date;
	create(d: string, locale?: string): Date;
	create(year: number, month: number, day: number, locale?: string): Date;
}

X.create("january 11th 2008");

declare var Date: {
	create(locale?: string): Date;
	create(d: string, locale?: string): Date;
	create(year: number, month: number, day: number, locale?: string): Date;
}

Date.create("january 11th 2008");

Specifically the error I get is:

"Error 1 The property 'create' does not exist on value of type '{ prototype: Date; parse(s: string): number; UTC(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): number; now(): number; (): string; new(): Date; new(value: number): Date; new(value: string): Date; new(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): Date; }' C:\some\path\sugar.d.ts\test.ts 9 6 test.ts"

And here is what lib.d.ts declares for Date:

 

// static
declare var Date: {
    new (): Date;
    new (value: number): Date;
    new (value: string): Date;
    new (year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): Date;
    (): string;
    prototype: Date;
    parse(s: string): number;
    UTC(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): number;
    now(): number;
}

// prototype
interface Date {
    toString(): string;
    toDateString(): string;
    toTimeString(): string;
...
...
}

Now finally if I do:

 

export declare var Date: {
    create(locale?: string): Date;
}

Then the typescript compiler will overwrite the lib.d.ts definitions of Date and the only function that appears is Date.create().  There is no way to 'mixin' or add functions statically as far as I can tell without wiping out the previous definitions.

Nov 21, 2012 at 4:01 AM

Ah thx the export keyword did the trick.

I think if they would use a DateStatic interface like the JQuery type def use, inside of the lib.d.ts instad of an Object. Then this stuff would be possible.

Nov 21, 2012 at 5:37 PM

Ah that dose not work. The compile dose not say anything about hat. But if you put it in a .d.ts file. Its still not available. :(

Nov 25, 2012 at 7:17 PM

This indeed seems like a bug to me too, will someone on the TypeScript team fix this?

I would really like to be able to add some custom static functions to the Date object.

Nov 25, 2012 at 7:52 PM

Ive added an change request which could help fix this. But no reaction yet.

http://typescript.codeplex.com/workitem/482

Nov 25, 2012 at 7:59 PM

"voted", hope it will get the attention it deserves :)

Nov 26, 2012 at 12:11 PM

This is one of those early issues that has not yet been fixed:

http://typescript.codeplex.com/discussions/400979

https://typescript.codeplex.com/workitem/176

 

I guess the TS team has become tired of saying "we're working on it" when they aren't really :-)

 

Our strategy at the moment has been to define a temporary type say Date2: 

class Date2 {    public static create() {    }}

Then once the fix is in place it should be a straightforward find-and-replace "Date2" with "Date".

Nov 26, 2012 at 4:29 PM

Seems like a good workaround for now, thanks for finding the previous issue nabog.

Dec 16, 2012 at 7:53 AM

Oh nice, the state of my issue has changed to verify http://typescript.codeplex.com/workitem/482

Dec 19, 2013 at 3:27 AM
I've created a quick gist with my current workaround/solution for using Sugar.js's static extensions. I'm still pretty new to TypeScript, so I'm sure its not the best solution, but I did not see a workaround example in my own search so maybe this will help someone.
Oct 7, 2014 at 3:33 AM
Related issue tracking on GitHub: https://github.com/Microsoft/TypeScript/issues/182
Oct 7, 2014 at 4:20 PM
Edited Oct 7, 2014 at 4:28 PM
For the record, I simply redefine the primitive types I need in a separate ts file, within the root of my namespace, and copy over the lib.d.ts declarations. Then, after the declaration, I "stitch up" the actual code for it using typical JS:
declare function __extends(d:{}, b:{});
// (note: the above only exists when you extend classes naturally,
//  otherwise you need to define it manually)
declare module NativeTypes {
    export interface IDate extends Date { }
    /*...etc...*/ 
}
declare module NativeStaticTypes {
    export var StaticDate : typeof Date; 
    /*...etc...*/ 
}
interface IStaticGlobals extends Window {
    Date: typeof NativeStaticTypes.StaticDate;
    /*...etc...*/ 
}
module MyApp {
    export var global: IStaticGlobals = (function () { return function () { }.constructor("return this"); })(); 
    export declare class Date implements NativeTypes.IDate {
        constructor();
        constructor(value: number); 
        constructor(dateString: string); 
        constructor(year, month, day, hour, minute, second, millisecond);
        /*... copy declarations from lib.d.ts (including static ones) ...*/
    }
    
    (function(){
        MyApp.Date = <any>function(/*...*/) {
            /*...*/
        };
        __extends(MyApp.Date, global.Date)
    })();
}
Then within my own namespace, Date becomes extendable, and instanceof Date also works as expected. While I could have created say "DateEx" or something, I can only rename my own code later, and not my API users, so this way, if TS later supports extending primitive types, I just remove this one file. ;)