##// END OF EJS Templates
Added tag v1.7.0 for changeset cede47727a1b
Added tag v1.7.0 for changeset cede47727a1b

File last commit:

r125:cede47727a1b v1.7.0 default
r126:57b65a506e4f default
Show More
readme.md
306 lines | 8.3 KiB | text/x-minidsrc | MarkdownLexer
cin
Converted to subproject djx, removed dojo-typings
r65 # @implab/djx
## 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<DjxWidgetBase<MyWidgetAttrs, MyWidgetEvents>>
) {
@bind({ node: "titleNode", type: "innerHTML" })
title = "";
@prototype()
counter = 0;
render() {
const Frame = (props: any) => <div>{props.children}</div>;
return <div
className="myWidget"
tabIndex={3}
style={ alignContent: "center", border: "1px solid" }
>
<h1 data-dojo-attach-point="titleNode"></h1>
<Frame>
<span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
<span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
</Frame>
</div>;
}
_onIncClick(e: MouseEvent) {
this.emit("count-inc", { bubbles: false });
}
_onDecClick() {
this.emit("count-dec", { bubbles: false });
}
}
```
## DESCRIPTION
This package provides you with the tools to glue your good-fellow dojo with modern
techniques of building the webapp. The core concept is to built around widgets and
using .tsx to write it. Here are 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 provide an easy way of using standard dojo widget
attribute bindings.
### djbase, @djclass
These two traits provide 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 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<Super>() {
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 at 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 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 similar, they 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 declare the interface with
desired methods and use the special form of `djbase<Super>()` without arguments.
### DjxWidgetBase<Attrs, Events>
cin
added observable subject producer
r125 This is the base class for the djx widgets. It declares the abstract method
`render()` which is used to render the content of the widget, like `_TemplatedMixin`.
This class extends `dijit/_WidgetBase` and contains logic from `_AttachMixin` thus
it is capable to handle `data-dojo-attach-*` attributes from the rendered markup.
```tsx
@djclass
export class MyFirstWidget extends djbase(DjxWidgetBase) {
render() {
return <h1>My first widget</h1>;
}
}
```
cin
Converted to subproject djx, removed dojo-typings
r65
### Markup (.tsx)
Add to your `tsconfig.json` the following options
```json
{
"compilerOptions": {
cin
added observable subject producer
r125 "types": [
"@implab/djx",
"@implab/dojo-typings"
],
"skipLibCheck": true,
cin
Converted to subproject djx, removed dojo-typings
r65 "experimentalDecorators": true,
"jsxFactory": "createElement",
"jsx": "react",
cin
added observable subject producer
r125 "target": "ES5", // minimal supported version
"lib": ["ES2015", "DOM"]
cin
Converted to subproject djx, removed dojo-typings
r65 }
}
```
Import `createElement` into your `.tsx` file
```ts
import { createElement } from "@implab/djx/tsx";
```
You are ready to go!
cin
added observable subject producer
r125 ### Adding reactive behavior: refs, watch(...) and watchFor(...)
This library adds some reactive traits to update the generated DOM of the widget.
Dojo 1.x adds some standard options to deal with dynamic changes:
* `data-dojo-attach-point` allows to get reference to an element (or a nested widget)
* widget attribute mappings, allows to bind widget's property to a property of
the element, referenced by `data-dojo-attach-point`.
The typical implementation of this technique would look like
```tsx
import { createElement } from "@implab/djx/tsx";
import {djclass, djbase, bind} from "@implab/djx/declare";
@djclass
export class MyFirstWidget extends djbase(DjxWidgetBase) {
// @bind will generate special attribute mapping
// _setCaptionAttr = { node: "captionNode", type: "innerHTML" }
@bind({ node: "captionNode", type: "innerHTML" })
caption = "My first widget";
render() {
return <h1 data-dojo-attach-point="captionNode"/>;
}
}
```
Despite this is a natural way for the dojo it has some disadvantages:
1. The compiler doesn't check existence of the attach-point.
2. Attribute mappings support only simple mappings, it's difficult to update the
complex rendition.
This library helps you to get both goals with special trait `watch(...)`
```tsx
import { createElement } from "@implab/djx/tsx";
import { djclass, djbase} from "@implab/djx/declare"
@djclass
export class MyFirstWidget extends djbase(DjxWidgetBase) {
caption = "My first widget";
render() {
return <h1>{watch(this,"caption", value => value)}</h1>;
}
}
```
In this example we replaced attach-point with simple call to `watch` function
which renders string value to text representation (text node). It will create a
rendition which will observe the `caption` property of the widget and update its
contents according to the value changes of the property.
The key feature of this approach that the rendering function within `watch` may
return a complex rendition.
```tsx
// inside some widget
render() {
return <section>
{watch(this,"user", value => value && [
<UserInfo user={value}/>,
<LogoutButton click={this._logoutClick}/>
])}
</section>;
}
private readonly _logoutClick = () => { /* do logout */ }
```
The `watch` function has two forms:
* `watch(stateful, prop, render)` - observes the specified property of the
`dojo/Stateful` object (or widget)
* `watch(observable, render)` - observes the specified observable. It supports
`rxjs` or `@implab/djx/observable` observables.
The `render` callback may return almost anything which will be converted to DOM:
* `boolean`, `null`, `undefined` - ignored,
* `string` - converted to text node,
* `array` - converted to DocumentFragment of its elements,
* DOM Nodes and widgets are left intact,
* any other kind of value will cause an error.