forked from DefinitelyTyped/DefinitelyTyped
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request DefinitelyTyped#2440 from vansimke/master
added dojo type definitions
- Loading branch information
Showing
65 changed files
with
513,175 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,294 @@ | ||
# Dojo Definitions Usage Notes | ||
|
||
## Overview | ||
Anyone that has used Dojo for any length of time has probably discovered three things: | ||
* Dojo is very powerful | ||
* Dojo can be challenging to learn | ||
* Dojo doesn't always play well with other | ||
|
||
Having said that, there are ways that Dojo can be coerced out of its shell to work with other JavaScript technologies. This README is intended to describe some techniques for getting the full power of Dojo to work in an environment where almost everything can take advantage of TypeScript. | ||
|
||
*Disclaimer*: Dojo is VERY big framework and, as such the type definitions are generated by a [tool](https://github.com/vansimke/DojoTypeDescriptionGenerator) from dojo's [API](dojotoolkit.org/api) docs. The generated files were then hand-polished to eliminate any import errors and clean up some obvious errors. This is all to say that the generated type definitions are not flawless and are not guaranteed to reflect the actual implementations. | ||
|
||
## Basic Usage | ||
A normal dojo module might look something like this: | ||
|
||
```js | ||
define(['dojo/request', 'dojo/request/xhr'], | ||
function (request, xhr) { | ||
... | ||
} | ||
); | ||
``` | ||
|
||
When using the TypeScript, you can write the following: | ||
|
||
```ts | ||
define(['dojo/request', 'dojo/request/xhr'], | ||
function (request: dojo.request, | ||
xhr: dojo.request.xhr) { | ||
... | ||
} | ||
); | ||
``` | ||
|
||
Inside of the define variable, both `request` and `xhr` will work as the functions that come from Dojo, only they are strongly typed. | ||
|
||
## Advanced Usage | ||
Dojo and TypeScript both use different and conflicting class semantics. This causes some issues when trying to create custom class modules that are strongly typed in other modules. The following technique is presented as **A** solution to the problem, but not necessarily the best one. Other ideas a welcomed! | ||
|
||
Using pure JavaScript, a class that has a base class and mixins can be defined in Dojo as follows: | ||
|
||
```js | ||
define(['dojo/_base/declare', 'dijit/_WidgetBase', 'dijit/_TemplatedMixin', 'dojo/request'], | ||
function(dojoDeclare, _WidgetBase, _TemplatedMixin, request) { | ||
var Foo = dojoDeclare([_WidgetBase, _TemplatedMixin], { | ||
templateString: '<div>Hello TypeScript</div', | ||
|
||
message: '', | ||
|
||
sayMessage: function() { | ||
alert(this.message); | ||
}, | ||
|
||
getServerInfo: function() { | ||
request.get('http://dojoAndTypeScriptTogetherAtLast.html', function(data) { | ||
console.log(data); | ||
}); | ||
} | ||
}); | ||
|
||
return Foo; | ||
} | ||
); | ||
``` | ||
|
||
The goal is to be able to describe the `Foo` class in a way that TypeScript can recognize, but also works with Dojo. This requires some hacks that will be introduced and explained as the problems are discovered. | ||
|
||
The first challenge that we run into is how to define the class. We will define that using standard TypeScript semantics as follows: | ||
|
||
```ts | ||
module App { | ||
export class Foo extends dijit._WidgetBase implements dijit._TemplatedMixin { | ||
constructor(public templateString= "<div>Hello TypeScript</div>", | ||
public message= "") { | ||
super(); | ||
} | ||
|
||
sayMessage() { | ||
alert(this.message); | ||
} | ||
|
||
getServerInfo() { | ||
request.get("http://dojoAndTypeScriptTogetherAtLast.html", (data: string) => { | ||
console.log(data); | ||
}); | ||
} | ||
|
||
} | ||
} | ||
``` | ||
|
||
This class is identical to the standard Dojo method, except that it is declared inside of a TypeScript module and it is declared using TypeScript instead of Dojo's `declare` method. Two problems arise however: | ||
1. `Foo` has an error because it doesn't honor the interface declared by dijit._TemplatedMixin | ||
2. `request` is undefined | ||
|
||
The first problem can be solved by adding the missing properties and methods, but this will only serve to clutter the code base over time. Instead, we are creating another base class that hides this requirement like so: | ||
|
||
```ts | ||
module App { | ||
export class Foo extends WidgetBaseWithTemplatedMixin { | ||
constructor(public templateString= "<div>Hello TypeScript</div>", | ||
public message= "") { | ||
super(); | ||
} | ||
|
||
sayMessage() { | ||
alert(this.message); | ||
} | ||
|
||
getServerInfo() { | ||
request.get("http://dojoAndTypeScriptTogetherAtLast.html", (data: string) => { | ||
console.log(data); | ||
}); | ||
} | ||
|
||
} | ||
|
||
export class WidgetBaseWithTemplatedMixin extends dijit._WidgetBase implements dijit._TemplatedMixin { | ||
"attachScope": Object; | ||
"searchContainerNode": boolean; | ||
"templatePath": string; | ||
"templateString": string; | ||
buildRendering(): {} | ||
destroyRendering(): {} | ||
getCachedTemplate(templateString: String, alwaysUseString: boolean, doc: HTMLDocument): {} | ||
|
||
} | ||
} | ||
``` | ||
|
||
Now the base class meets TypeScript's requirements so it is happy. This class could easily be moved out to a general add-in file so that it can be created and forgotten since it is only here to make TypeScript happy. | ||
|
||
The second problem that we had as that `request` is undefined. This is going to take a bit more trickery as shown below: | ||
|
||
```ts | ||
module App { | ||
export class Foo extends WidgetBaseWithTemplatedMixin { | ||
constructor(public templateString= "<div>Hello TypeScript</div>", | ||
public message= "") { | ||
super(); | ||
} | ||
|
||
public request: dojo.request; | ||
|
||
sayMessage() { | ||
alert(this.message); | ||
} | ||
|
||
getServerInfo() { | ||
this.request.get("http://dojoAndTypeScriptTogetherAtLast.html").then((data: string) => { | ||
console.log(data); | ||
}); | ||
} | ||
|
||
} | ||
|
||
export class WidgetBaseWithTemplatedMixin extends dijit._WidgetBase implements dijit._TemplatedMixin { | ||
public static getPrototype(deps: Object) { | ||
if (deps) { | ||
for (var i in deps) { | ||
this.prototype[i] = deps[i]; | ||
} | ||
|
||
return this.prototype; | ||
} | ||
} | ||
|
||
"attachScope": Object; | ||
"searchContainerNode": boolean; | ||
"templatePath": string; | ||
"templateString": string; | ||
buildRendering(): {} | ||
destroyRendering(): {} | ||
getCachedTemplate(templateString: String, alwaysUseString: boolean, doc: HTMLDocument): {} | ||
|
||
} | ||
} | ||
|
||
|
||
define(['dojo/_base/declare', 'dijit/_WidgetBase', 'dijit/_TemplatedMixin', 'dojo/request'], | ||
function (dojoDeclare, _WidgetBase, _TemplatedMixin, request) { | ||
var deps = { | ||
request: request | ||
}; | ||
|
||
var Foo = dojoDeclare([_WidgetBase, _TemplatedMixin], App.Foo.getPrototype(deps)); | ||
|
||
return Foo; | ||
} | ||
); | ||
``` | ||
|
||
Yes, I know - pretty crazy right. But, we're getting close... | ||
|
||
In the Dojo module, we are building an object that contains references to each of the dependencies. We are then passing that object into the static method `getPrototype` that we have added to the base class. This method takes an object literal and mixes it into the class's prototype. In this way, the module dependencies are made available to the TypeScript class via its prototype. The last thing we need to do is change the `getServerInfo()`'s call to `request` to be a `this.request` call since it is calling through its prototype instead of the ambient object that is used in Dojo. | ||
|
||
Okay, great. TypeScript is happy. Everything should be working right? Wrong. | ||
|
||
We have two more problems that are not apparent until the code is actually executed. They are both related to our usage of the `extends` keyword that we used to show that our `Foo` class extends from `dijit._WidgetBase`. | ||
|
||
The first problem is that, as stated previously, TypeScript has its own implementation of a class system in JavaScript. When one class extends another, TypeScript injects the following snippet into the module: | ||
|
||
```js | ||
var __extends = this.__extends || function (d, b) { | ||
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; | ||
function __() { this.constructor = d; } | ||
__.prototype = b.prototype; | ||
d.prototype = new __(); | ||
}; | ||
``` | ||
|
||
This method is called in a closure that wraps the class definition and mixes the parent's prototype and owned properties into the child class. However, this won't work in our case, because our base class is `dijit._WidgetBase` which doesn't actually exist in the global namespace (where TypeScript expects it). This is because we are still using Dojo's class system (via `declare`). This is an important, and confusing, point. Our class is actually being constructed by Dojo using declare. However, we are working with the class as if it was created in the way the TypeScript expects. In short, this means that we don't actually need the `__extends` function to work, but something needs to be there so that the constructor function doesn't die. The solve is actually relatively easy: In the main HTML page, add this function before the tag that includes `dojo.js`: | ||
|
||
```js | ||
var __extends = function (d, b) { | ||
if (d && b) { | ||
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; | ||
function __() { | ||
this.constructor = d; | ||
} | ||
|
||
__.prototype = b.prototype; | ||
d.prototype = new __(); | ||
} | ||
}; | ||
``` | ||
|
||
All this does is check to see if `d` and `b` are defined before running. Since TypeScript won't override __extends, it will allow us to override the default implementation. | ||
|
||
Okay, only one more thing to deal with: the call to super. This issue is also related to TypeScript's method for handling inheritance. After calling `__extends`, the generated constructor function will call the parent's constructor function. Once again, we are hit by the fact that our base class (`dijit._WidgetBase`) doesn't actually exist where TypeScript is expecting it. The only way around this is to give TypeScript something to call. This simplest thing to do is to add a no-op function for TypeScript to call. In short add this: | ||
|
||
```js | ||
var dijit = dijit || {}; | ||
dijit._WidgetBase = function() {} | ||
``` | ||
|
||
Into the page after Dojo bootstraps, but before our module loads. The simplest way to do this is to create a little module that does this and added it to the array of modules loaded in the `define()` call of the module. | ||
|
||
Okay, so things look pretty messy right now. There are several hacks and tricks that we have to play in order to allow TypeScript and Dojo to work together. The nice thing about most of this is that it can all be shoved into a single helper module and never thought of again. Here is an example of what that module would look like: | ||
|
||
```ts | ||
"use strict"; | ||
|
||
define([], function () { }); | ||
|
||
var __extends = function (d, b) { | ||
if (d && b) { | ||
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; | ||
function __() { | ||
this.constructor = d; | ||
} | ||
|
||
__.prototype = b.prototype; | ||
d.prototype = new __(); | ||
} | ||
}; | ||
|
||
window['dojo'] = {}; | ||
window['dijit'] = { | ||
_WidgetBase: function () { | ||
|
||
} | ||
}; | ||
|
||
module Base { | ||
function getPrototype(type: Function, deps: Object): Object { | ||
if (deps) { | ||
for (var i in deps) { | ||
type.prototype[i] = deps[i]; | ||
} | ||
|
||
return this.prototype; | ||
} | ||
} | ||
export class WidgetBaseWithTemplatedMixin extends dijit._WidgetBase implements dijit._TemplatedMixin { | ||
public static getPrototype(deps: Object): Object { | ||
return getPrototype(this, deps); | ||
} | ||
|
||
"attachScope": Object; | ||
"searchContainerNode": boolean; | ||
"templatePath": string; | ||
"templateString": string; | ||
buildRendering() { } | ||
destroyRendering() { } | ||
getCachedTemplate(templateString: String, alwaysUseString: boolean, doc: HTMLDocument) { } | ||
|
||
} | ||
} | ||
``` | ||
|
||
This module can then be added to whenever we have another base class / mix-in combination (e.g. dijit/_WidgetBase, dijit/_TemplatedMixin, and dijit/_WidgetsInTemplateMixin). When done this way, the only regularly visible changes that we have to do is to compose the hash of dependencies and call the `getPrototype` as the last argument to `declare`. | ||
|
||
|
Oops, something went wrong.