Unable to get CustomEvent polyfill through the TypeScript compiler

Topics: General
May 28, 2014 at 9:49 PM
Edited May 28, 2014 at 9:52 PM
I have a small app prototype I'm working on; and it includes the CustomEvent polyfill from here:

https://developer.mozilla.org/en/docs/Web/API/CustomEvent

I'm trying to rename all my .js files to .ts so that I can being using TypeScript; however I'm just completely failing to get it compiling. I'm trying not to make too many changes to the code (TypeScript was sold as being able to progressively introduce, without a bunch of up-front work), though I am open to rewriting it directly in TypeScript if required.

The code in I'm trying to convert is (from the page linked above);
(function () {
  function CustomEvent ( event, params ) {
    params = params || { bubbles: false, cancelable: false, detail: undefined };
    var evt = document.createEvent( 'CustomEvent' );
    evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
    return evt;
   };

  CustomEvent.prototype = window.Event.prototype;

  window.CustomEvent = CustomEvent;
})();

// and call it:
window.dispatchEvent(new CustomEvent('doStuff', { detail: 1 }));
I've managed to get rid of all the red squigglies in the Playground:
interface Window {
    Event : Event;
    CustomEvent(event : any, params : any) : Event;
}
interface Event {
    prototype : Event;
}
declare var CustomEvent: {
    new (event : string, detail : any) : CustomEvent;
}

(function () {
  function CustomEvent ( event, params ) {
    params = params || { bubbles: false, cancelable: false, detail: undefined };
    var evt = <CustomEvent>document.createEvent('CustomEvent');
    evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
    return evt;
   }

  CustomEvent.prototype = window.Event.prototype;

  window.CustomEvent = CustomEvent;
})();

// and call it:
window.dispatchEvent(new CustomEvent('doStuff', { detail: 1 }));
However, I just cannot get this to compile in Visual Studio. It always complains at the CustomEvent decleration (presumably because it's already in lib.d.ts; I've no idea why it works in the Playground):
 
Subsequent variable declarations must have the same type. Variable 'CustomEvent' must be of type '{ prototype: CustomEvent; new(): CustomEvent; }', but here has type 'new(event: string, detail: any) => CustomEvent'
 
What am I doing wrong?
May 28, 2014 at 10:14 PM
Edited May 28, 2014 at 10:28 PM
The lib.d.ts does not have the CustomEvent signature for DOM4 support in this case (perhaps submit a request). It could be a very new addition.

Anyhow, you can try it this way:
module pollyfills {
    export interface StaticCustomEvent {
        prototype: CustomEvent;
        new (type: string, eventInitDict?: {}): CustomEvent;
    } 
    
    declare var CustomEvent: StaticCustomEvent;
    
    (function () {
      function CustomEvent ( event, params ) {
        params = params || { bubbles: false, cancelable: false, detail: undefined };
        var evt = <CustomEvent>document.createEvent('CustomEvent');
        evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
        return evt;
       }
    
      CustomEvent.prototype = Event.prototype;
    
      (<any>window).CustomEvent = CustomEvent;
    })();
    
    window.dispatchEvent(new CustomEvent('doStuff', { detail: 1 }));
}
Note: This does not change the global scope "CustomEvent" static signature, which cannot accept parameters. Outside the pollyfill, you may need to use a wrapper method - perhaps something like export var CustomEvent: StaticCustomEvent = ...detected implementation...; in the 'pollyfills' module.

Edit:
Note: IE11 does not support 'CustomEvent' as a function call. This may be why the lib.d.ts signature is as it is.
May 28, 2014 at 10:38 PM
dannytuppeny wrote:
I'm trying to rename all my .js files to .ts so that I can begin using TypeScript; however I'm just completely failing to get it compiling. I'm trying not to make too many changes to the code (TypeScript was sold as being able to progressively introduce, without a bunch of up-front work), though I am open to rewriting it directly in TypeScript if required.
Unfortunately, because TypeScript imposes a typing layer over top the common JavaScript objects, when those objects are in use in your JS code, it immediately introduces types, and will most likely cause a bunch of errors to pop up. This can be a good thing, as it can possibly help you weed out potential bugs you never thought of. The other thing is that TypeScript's library, IMHO, tries to support standardized signatures common to most browsers (hence the CustomEvent declaration), so if you are doing something on the "bleeding edge", you will most likely have to use a wrapper method as a work-around for now.
May 30, 2014 at 1:09 PM
I'm struggling to get this to work; I don't fully understand about the "wrapper method"; and it's starting to sound a lot like a hack? :-/

One thing I don't full understand, is why the code seems to work fine in the Playground? :/
May 30, 2014 at 2:35 PM
It didn't work for me in the playground. 'CustomEvent' cannot take parameters.

The only way (not really a hack) is to keep ALL your code in your own name space (module), then you can easily replace the lib.d.ts definitions.
May 30, 2014 at 6:02 PM
The best solution I've come up with so far (with Mark Rendle's help) involved just two slight "hacks":

When raising the event, use window.CustomEvent, since I can't replace the "global" CustomEvent definition:
new window.CustomEvent('myEvent', { detail: 1 })
Cast to <any> and back to be able to apply the constructor function to Window:
interface CustomEvent {
    new (event: string, params?: CustomEventParams);
}

interface Window {
    CustomEvent: CustomEvent;
}

(function () {
    function CustomEvent(event: string, params?: CustomEventParams) : CustomEvent {
    // ...
     }
 
    window.CustomEvent = <CustomEvent><any>CustomEvent;
})();
I don't think the first can be avoided without a change to TypeScript; and I can't find a way to avoid the nasty casts in the second; apparently this is a frequent TS hack :-/
May 30, 2014 at 6:54 PM
Edited May 30, 2014 at 7:25 PM
IMHO, TS should have created static interfaces for the global vars, and not direct type literals. This could at least allow for adding new static methods, or new overloads.
May 30, 2014 at 7:23 PM
Edited May 30, 2014 at 7:27 PM
BTW: Just a warning - with that hack, you are polluting the instance type "CustomEvent" with a function call signature, which is wrong. Intellisense will show "window.CustomEvent.initCustomEvent()", which is not correct. The point of my pollyfill example above was to prevent this (and you should wrap your app in your own module anyhow).
May 30, 2014 at 7:38 PM
FYI: Somewhat related post (for reference): https://typescript.codeplex.com/discussions/547099#post1251363
May 30, 2014 at 7:43 PM
I would still suggest considering putting all your code in your own namespace, then you will be able to provide your own definition, like so:
module myApp {
    declare var CustomEvent: {
        prototype: CustomEvent;
        new (event: string, params?: CustomEventParams);
    }

    // ... the reset of your app ...
}
May 30, 2014 at 7:52 PM
The code is intended to be a polyfill for browsers that don't implement CustomEvent this way; so adding it via a module is a little naff (though having to do window.CustomEvent isn't much better).
May 30, 2014 at 8:10 PM
Edited May 30, 2014 at 8:12 PM
I'm assuming your code is not embedded in the html pages, and exists as .ts files? To prevent polluting the global scope, I think it's very good practice to put all your app code in a shared module scope (these are for scopes only). I wasn't referring to a module only for the pollyfill, but instead, putting all your app code in the same module namespace means you don't have to worry about "Window.CustomEvent" at all, and you can continue coding as normal. In the future, if the global 'CustomEvent' signature changes, your code will be still best kept in a module scope, and you can remove the "declare var CustomEvent..." that was added.