dojo-widgets
A core widget library for Dojo.
WARNING This is alpha software. It is not yet production ready, so you should use at your own risk.
For more background on dojo-widgets, there is a document describing the widgeting system.
Usage
To use dojo-widgets in a project install the package along with the required peer dependencies.
npm install dojo-widgets # peer dependencies npm install dojo-hasnpm install dojo-shimnpm install dojo-corenpm install dojo-compose
To use dojo-widgets, import the module in the project. For more details see features below.
;
or use the dojo cli to create a complete Dojo skeleton application.
Features
dojo-widgets are based on a virtual DOM implementation named Maquette as well as some foundational classes provided in dojo-compose.
The smallest dojo-widgets
example looks like this:
;projector.children = ;projector.append;
It renders a h1
element saying "Hello, Dojo!" on the page. See the following sections for more details.
Principles
These are some of the important principles to keep in mind when developing widgets:
- the widget render function should never be overridden
- with the exception of the top level projector you should never have to deal with widget instances.
- hyperscript should always be written using the dojo-widgets
v
helper function. state
should never be set outside of the widget instance.
Base Widget
The class createWidgetBase
provides all base dojo-widgets functionality including caching and widget lifecycle management. It can be used directly or extended to create custom widgets.
Widget API
A subset of the Widget
API are intended to be overridden in scenarios where the default implemented behviour does not suffice.
Function | Description | Default Behaviour |
---|---|---|
getNode | Returns the top level node of a widget | Returns a DNode with the widgets tagName , the result of this.getNodeAttributes and this.children |
getChildrenNodes | Returns the child node structure of a widget | Returns the widgets children DNode array |
nodeAttributes | An array of functions that return VNodeProperties to be applied to the top level node | Returns attributes for data-widget-id , classes and styles using the widget's specified state (id , classes , styles ) at the time of render |
diffProperties | Diffs the current properties against the previous properties and returns the updated/new keys in an array | Performs a shallow comparison using === of previous and current properties and returns an array of the keys. |
applyChangedProperties | Gets called to apply updated/new properties | If there are updated properties they are directly set onto the widgets state using setState . |
To customise the widget an optional options
argument can be provided with the following interface.
Type: WidgetOptions<WidgetState, WidgetProperties>
- All properties are optional.
Property | Type | Description |
---|---|---|
id | string | identifier for the widget |
stateFrom | StoreObservablePatchable | Observable that provides state for the widget |
listeners | EventedListenersMap | Map of listeners for to attach to the widget |
tagName | string | Override the widgets tagname |
properties | WidgetProperties | Props to be passed to the widget. These can be used to determine state internally |
nodeAttributes | Function[] | An array of functions that return VNodeProperties to be applied to the VNode |
A widgets' state
should never be directly set outside of the instance. In order to manipulate widget state
, properties
should be updated such as widget.properties = { 'foo': 'bar' }
and then during the next render the new or updated properties are passed to instance#applyChangedProperties
.
As a convenience, all event handlers are automatically bound to the widget instance, so state and other items on the instance can be easily accessed.
Simple Widgets
To create a basic widget createWidgetBase
can be used directly by importing the class.
; ;
The widget creates the following DOM element:
The following example demonstrates how id
, classes
and styles
are applied to the generated DOM.
; ;
The widget creates the following DOM element:
Alternatively state can be derived directly from an observable store passed via stateFrom
. The following code will create the same DOM element as the above example.
; ;
d
d
is the module that provides the functions and helper utilities that can be used to express widget structures within Dojo 2. This structure constructed of DNode
s (DNode
is the intersection type of HNode
and WNode
).
When creating custom widgets, you use the v
and w
functions from the d
module.
It is imported by:
;
The argument and return types are available from dojo-widgets/interfaces
as follows:
;
v
v
is an abstraction of Hyperscript that allows dojo 2 to manage caching and lazy creation.
Creates an element with the specified tag
vtag: string: HNode;
where tag
is in the form: element.className(s)#id, e.g.
h2 h2.foo h2.foo.bar h2.foo.bar#baz h2#baz
classNames
must be period (.) delimited if more than 1 class is specified.
Please note, both the classes
and id
portions of the tag
are optional.
The results of the invocations above are:
h2 (<h2></h2>)
h2.foo (<h2 class="foo"></h2>)
h2.foo.bar (<h2 class="foo bar"></h2>)
h2.foo.bar#baz (<h2 class="foo bar" id="baz"></h2>)
h2#baz (<h2 id="baz"></h2>)
Creates an element with the tag
with the children specified by the array of DNode
, VNode
, string
or null
items.
vtag: string, children: DNode | null: HNode;
Creates an element with the tagName
with VNodeProperties
options and optional children specified by the array of DNode
, VNode
, string
or null
items.
vtag: string, properties: VNodeProperties, children?: DNode | null: HNode;
registry
The d module exports a global registry
to be used to define a factory label against a WidgetFactory
, Promise<WidgetFactory>
or () => Promise<WidgetFactory
.
This enables consumers to specify a string
label when authoring widgets using the w
function (see below) and allows the factory to resolve asyncronously (for example if the module had not been loaded).
;; // registers the widget factory that will be available immediatelyregistry.define'my-widget-1', createMyWidget; // registers a promise that is resolving to a widget factory and will be// available as soon as the promise resolves.registry.define'my-widget-2', Promise.resolvecreateMyWidget; // registers a function that will be lazily executed the first time the factory// label is used within a widget render pipeline. The widget will be available// as soon as the promise is resolved after the initial get.registry.define'my-widget-3',Promise.resolvecreateMyWidget;
It is recommmended to use the factory registry when defining widgets using w
in order to support lazy factory resolution when required. Example of registering a function that returns a Promise
that resolves to a Factory
.
; registry.define'my-widget',;
w
w
is an abstraction layer for dojo-widgets that enables dojo 2's lazy instantiation, instance management and caching.
Creates a dojo-widget using the factory
and options
.
wfactory: string | ComposeFactory<W, O>, options: O: WNode;
Creates a dojo-widget using the factory
, options
and children
wfactory: string | ComposeFactory<W, O>, options: O, children: DNode | null: WNode;
Example w
constucts:
wcreateFactory, options, children;w'my-factory', options, children;
Authoring Widgets
To create custom reusable widgets you can extend createWidgetBase
.
A simple widget with no children such as a label
widget can be created like this:
;;;; ; ; ;
With its usages as follows:
; ;
To create structured widgets override the getChildrenNodes
function.
;;;; ; ; ; ; ; ;
Event Handlers
The recommended pattern for event handlers is to declare them on the widget class, referencing the function using this
most commonly within getChildrenNodes
or a nodeAttributes
function.
Event handlers can be internal logic encapsulated within a widget as shown in the first example or they can delegate to a function that is passed via properties
as shown in the second example.
internally defined handler
;
Handler passed via properties
;
Projector
To render widgets they must be appended to a projector
. It is possible to create many projectors and attach them to Elements
in the DOM
, however projectors
must not be nested.
The projector works in the same way as any widget overridding getChildrenNodes
when createProjector
class is used as the base for a custom widget (usually the root of the application).
In addition when working with a projector
you can also set the children
directly.
The standard WidgetOptions
are available and also createProjector
adds two additional optional properties root
and cssTransitions
.
root
- TheElement
that the projector attaches to. The default value isdocument.body
cssTransitions
- Set totrue
to support css transitions and animations. The default value isfalse
.
Note: If cssTransitions
is set to true
then the projector expects Maquette's css-transitions.js
to be loaded.
In order to attach the createProjector
to the page call either .append
, .merge
or .replace
depending on the type of attachment required and it returns a promise.
Instantiating createProjector
directly:
;;;;; ; projector.children = ; projector.append.then;
Using the createProjector
as a base for a root widget:
;;;;; ; ;
Using the custom widget assuming a class called createApp.ts
relative to its usages:
; ; app.append.then;
Internationalization
Widgets can be internationalized by mixing in dojo-widgets/mixins/createI18nMixin
. Message bundles are localized by passing them to localizeBundle
. If the bundle supports the widget's current locale, but those locale-specific messages have not yet been loaded, then the default messages are returned and the widget will be invalidated once the locale-specific messages have been loaded. Each widget can have its own locale by setting its state.locale
; if no locale is set, then the default locale as set by dojo-i18n
is assumed.
.mixincreateI18nMixin .mixin; ;
Dojo Widget Components
A selection of core reusable widgets are provided for convenience that are fully accessible and open to internationalization.
// TODO - list core components here.
How Do I Contribute?
We appreciate your interest! Please see the Dojo Meta Repository for the Contributing Guidelines and Style Guide.
Installation
To start working with this package, clone the repository and run npm install
.
In order to build the project run grunt dev
or grunt dist
.
Testing
Test cases MUST be written using Intern using the Object test interface and Assert assertion interface.
90% branch coverage MUST be provided for all code submitted to this repository, as reported by istanbul’s combined coverage results for all supported platforms.
To test locally in node run:
grunt test
To test against browsers with a local selenium server run:
grunt test:local
To test against BrowserStack or Sauce Labs run:
grunt test:browserstack
or
grunt test:saucelabs
Licensing Information
© 2016 JS Foundation. New BSD license.