diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,7 @@ configureTsMain { configureTsTest { compilerOptions { typeRoots = [] - types = ["requirejs", sources.main.output.typingsDir.get().toString()+"/legacy" ] + types = ["requirejs", sources.main.output.typingsDir.get().toString() ] } } diff --git a/readme.md b/readme.md --- a/readme.md +++ b/readme.md @@ -1,3 +1,198 @@ # @implab/djx -TODO \ No newline at end of file +## SYNOPSIS + +```tsx +import { djbase, djclass, bind, prototype, AbstractConstructor } from "@implab/djx/declare"; + +import { DjxWidgetBase } from "@implab/djx/tsx/DjxWidgetBase"; +import { createElement } from "@implab/djx/tsx"; + +interface MyWidgetAttrs { + title: string; + + counter: number; +} + +interface MyWidgetEvents { + "count-inc": Event; + + "count-dec": Event; +} + + +@djclass +export class MyWidget extends djbase( + DjxWidgetBase as AbstractConstructor> +) { + + @bind({ node: "titleNode", type: "innerHTML" }) + title = ""; + + @prototype() + counter = 0; + + render() { + const Frame = (props: any) =>
{props.children}
; + return
+

+ + this._onIncClick(e)}>[+] + this._onDecClick()}>[-] + +
; + } + + _onIncClick(e: MouseEvent) { + this.emit("count-inc", { bubbles: false }); + } + + _onDecClick() { + this.emit("count-dec", { bubbles: false }); + } +} + +``` + +## DESCRIPTION + +This package provides you with tools to glue your good-fellow dojo with modern +techniques of building the webapp. The core concept is built around widgets and +using .tsx to write it. Here some features: + +* `djbase()`, `@djaclass` - traits to declare your classes with `dojo/_base/declare` +* `@implab/djx/tsx` - traits to build the rendering of your widgets with tsx +* `DjxWidgetBase` - abstract class which supports tsx markup and + `data-dojo-attach-*` attributes. +* `@bind(...)` - annotations provides an easy way of using standard dojo widget + attribute bindings. + +### djbase, @djclass + +These two traits provides convenient way of using `dojo/_base/declare` in Typescript +for declaring your classes. + +`djbase(...constructors)` - this method accepts a list of constructors in its +parameters and returns the **fake** base type which then can be used to derive +your own class. This allows you to provide the Typescript with the correct +information about the base type and even use `super`!. The only one caveat of +this approach is that you **MUST** decorate your class with `@djclass` annotation. + +Consider the following example: + +```ts +import { djbase, djclass } from "@implab/djx/declare"; +import { FooMixin } from "./FooMixin"; +import { BarMixin } from "./BarMixin"; +import { BoxMixin } from "./BoxMixin"; + +@djclass +export class Baz extends djbase(FooMixin, BarMixin, BoxMixin) { + writeHello(out: string[]) { + out.push("-> Baz"); + + super.writeHello(out); + + out.push("<- Baz"); + } +} + +``` + +All mixins are declared like the one below: + +```ts +import { djclass, djbase } from "@implab/djx/declare"; + +interface Super { + writeHello(out: string[]): void; + +} + +@djclass +export class BarMixin extends djbase() { + writeHello(out: string[]) { + out.push("-> Bar"); + + super.writeHello(out); + + out.push("<- Bar"); + } +} +``` + +finally create an instance and call the `writeHello` method + +```ts +const baz = new Baz(); + +const data: string[] = []; +baz.writeHello(data); + +console.log(data.join("\n")); + +``` + +you will get the following output: + +```text +-> Baz +-> Box +-> Bar +-> Foo +<- Foo +<- Bar +<- Box +<- Baz +``` + +Let's take a closer look to the `Baz` declaration it uses `djbase` to derive +from three mixins and the class is decorated with `@djclass` to accomplish the +declaration and make a real constructor. + +To allow an access to the next sibling method (in terms of multiple inheritance) +Dojo provides `this.inherited(arguments)` method but this approach leads to the +problem with 'strict' mode of ES5 and eliminates the type information about a +calling method. This library solves the problem calling inherited/next method by +utilizing `super` keyword. Under the hood there are proxy methods generated in +the prototype of the declared class which make calls to `this.inherited(...)` +method. This technique is compatible with 'strict' mode. + +Mixins are declared the similar, they are also may have the base types although +the most common case is declaring the mixin without any base classes. To allow +the mixin to access the next method you should declare the interface with +desired methods and use the special form of `djbase()` without arguments. + +### DjxWidgetBase + +TODO + +### Markup (.tsx) + +Add to your `tsconfig.json` the following options + +```json +{ + "compilerOptions": { + "types": ["@implab/djx"], + "experimentalDecorators": true, + "jsxFactory": "createElement", + "jsx": "react", + } +} + +``` + +Import `createElement` into your `.tsx` file + +```ts +import { createElement } from "@implab/djx/tsx"; +``` + +You are ready to go! + +TODO diff --git a/src/main/typings/index.d.ts b/src/main/typings/index.d.ts --- a/src/main/typings/index.d.ts +++ b/src/main/typings/index.d.ts @@ -4,9 +4,21 @@ declare namespace JSX { interface DjxIntrinsicAttributes { + /** alias for className */ class: string; + + /** specifies the name of the property in the widget where the the + * reference to the current object will be stored + */ "data-dojo-attach-point": string; + + /** specifies handlers map for the events */ "data-dojo-attach-event": string; + + [attr: string]: any; + } + + interface DjxIntrinsicElements { } type RecursivePartial = T extends string | number | boolean | null | undefined | Function ? @@ -19,16 +31,22 @@ declare namespace JSX { type NotMatchingMemberKeys = { [K in keyof T]: T[K] extends U ? never : K; }[keyof T]; + + type ExtractMembers = Pick>; + + type ExcludeMembers = Pick>; + type ElementAttrNames = NotMatchingMemberKeys any>; type ElementAttrType = K extends keyof E ? RecursivePartial : string; - type LaxElement = E & { } + type LaxElement = ExcludeMembers, (...args: any[]) => any> & DjxIntrinsicAttributes; - type LegacyElementAttributes = { - [attr in ElementAttrNames]?: ElementAttrType; - } | Partial; - interface IntrinsicElements { - [tag: keyof HTMLElementTagNameMap]: LegacyElementAttributes; + type LaxIntrinsicElementsMap = { + [tag in keyof HTMLElementTagNameMap]: LaxElement + } & DjxIntrinsicElements; + + type IntrinsicElements = { + [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial; } } diff --git a/src/test/ts/view/MyWidget.tsx b/src/test/ts/view/MyWidget.tsx --- a/src/test/ts/view/MyWidget.tsx +++ b/src/test/ts/view/MyWidget.tsx @@ -27,17 +27,12 @@ export class MyWidget extends djbase(Djx render() { const Frame = (props: any) =>
{props.children}
; - return
+ return

this._onIncClick(e)}>[+] - this._onDecClick()}>[-] + this._onDecClick()}>[-] - - - - -
; } diff --git a/src/test/tsconfig.json b/src/test/tsconfig.json --- a/src/test/tsconfig.json +++ b/src/test/tsconfig.json @@ -8,6 +8,6 @@ "../main/ts", "../main/typings" ], - "types": ["requirejs", "../main/typings/legacy", "dojo-typings"] + "types": ["requirejs", "../main/typings", "dojo-typings"] } } \ No newline at end of file