npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@implab/djx

v1.10.3

Published

Supports using dojo version 1 with typescript and .tsx files

Downloads

17

Readme

@implab/djx

SYNOPSIS

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:

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:

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

const baz = new Baz();

const data: string[] = [];
baz.writeHello(data);

console.log(data.join("\n"));

you will get the following output:

-> 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>

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.

@djclass
export class MyFirstWidget extends djbase(DjxWidgetBase) {
    render() {
        return <h1>My first widget</h1>;
    }
}

Markup (.tsx)

Add to your tsconfig.json the following options

{
    "compilerOptions": {
        "types": [
            "@implab/djx",
            "@implab/dojo-typings"
        ],
        "skipLibCheck": true,
        "experimentalDecorators": true,
        "jsxFactory": "createElement",
        "jsx": "react",
        "target": "ES5",
        "lib": ["ES2015", "DOM"]
    }
}

Import createElement into your .tsx file

import { createElement } from "@implab/djx/tsx";

You are ready to go!

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

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(...)

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.

// 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.

The watch method allows to observe a single value, for the large sets of data this isn't suitable well and may lead to performance issues. Dojo provides observable stores to being able to track individual changes. The library provides watchFor(observable, render) method to render observable query results and handle changes on per item basis.

// inside some widget
staff = new Observable(new Memory<Employee>()),

getStuff() {
    return this.staff.query();
}

addEmployee(employee: Employee) {
    this.staff.add(employee); // the rendition will update automatically
}

render() {
    return <table>
        <thead>
            <tr><th>Name</th><th>Position</th><th>Salary</th></tr>
        </thead>
        <tbody>
        {watchFor(this.getStaff(), ({name, position, salary}) => 
            <tr><td>{name}</td><td>{position}</td><td>{salary}</td></tr>
        )}
        </tbody>
    </table>
}