This repo provides users with the ability to write their own Dojo 2 widgets.
We also provide a suite of pre-built widgets to use in your applications: (@dojo/widgets).
WARNING This is beta software. While we do not anticipate significant changes to the API at this stage, we may feel the need to do so. This is not yet production ready, so you should use at your own risk.
- Usage
- Features
- How Do I Contribute?
- Licensing Information
To use @dojo/widget-core, install the package along with its required peer dependencies:
npm install @dojo/widget-core
# peer dependencies
npm install @dojo/has
npm install @dojo/shim
npm install @dojo/core
npm install @dojo/i18n
npm install maquette
You can also use the dojo cli to create a complete Dojo 2 skeleton application.
Constructing your own widgets (Custom widgets) is simple and straightforward.
The smallest @dojo/widget-core
example looks like this:
class MyWidget extends WidgetBase<WidgetProperties> {
render() {
return v('h1', [ 'Hello, Dojo!' ]);
}
}
const Projector = ProjectorMixin(MyWidget);
const projector = new Projector();
projector.append(root);
This code renders a h1
element onto the page, that says "Hello, Dojo!".
All widgets in Dojo 2 are designed using key reactive architecture concepts. These concepts include unidirectional data flow, inversion of control and property passing.
Dojo 2's widget-core is built with TypeScript, leveraging Class mixins to construct and manipulate traits and mixins.
We also make use of a VirtualDOM (VDOM) in Dojo 2.
In order to interact with our VDOM, you need to pass it HyperScript.
In Dojo 2 we provide 2 functions that make interacting with the VDOM, easy and intuitive: v
and w
.
These functions express structures that will be passed to the VDOM.
v
creates nodes that represent DOM tags, e.g. div
, header
etc.
This function allows Dojo 2 to manage lazy hyperscript creation and element caching.
w
creates Dojo 2 widgets or custom widget.
This function provides support for lazy widget instantiation, instance management and caching.
The v
& w
functions are available from the @dojo/widget-core/d
package.
import { v, w } from '@dojo/widget-core/d';
The argument and return types for v
and w
are available from @dojo/widget-core/interfaces
, and are as follows:
import { DNode, HNode, WNode } from '@dojo/widget-core/interfaces';
The following code creates an element with the specified tag
v(tag: string): HNode[];
The following code renders an element with the tag
and children
.
v(tag: string, children: DNode[]): HNode[];
The following code renders an element with the tag
, properties
and children
.
v(tag: string, properties: VirtualDomProperties, children?: DNode[]): HNode[];
As well as interacting with the VDOM by passing it HyperScript, you can also pass it Dojo 2 Widgets or Custom Widgets using the w
function.
The following code creates a widget using the widgetConstructor
.
w<P extends WidgetProperties>(widgetConstructor: string | WidgetBaseConstructor<P>): WNode[];
The following code creates a widget using the widgetConstructor
and properties
.
w<P extends WidgetProperties>(widgetConstructor: string | WidgetBaseConstructor<P>, properties: P): WNode[];
The following code creates a widget using the widgetConstructor
and children
.
w<P extends WidgetProperties>(widgetConstructor: string | WidgetBaseConstructor<P>, children: DNode[]): WNode[];
The following code creates a widget using the widgetConstructor
, properties
and children
w<P extends WidgetProperties>(widgetConstructor: string | WidgetBaseConstructor<P>, properties: P, children: DNode[]): WNode[];
Example w
constructs:
w(WidgetClass);
w(WidgetClass, properties);
w(WidgetClass, children);
w(WidgetClass, properties, children);
w('my-widget');
w('my-widget', properties);
w('my-widget', children);
w('my-widget', properties, children);
The example above that uses a string for the widgetConstructor
, is taking advantage of the widget registry functionality.
The widget registry allows for the lazy loading of widgets.
In additional to the programatic functions v
and w
, widget-core optionally supports the use of the jsx
syntax known as tsx
in TypeScript.
To start to use jsx
in your project the widgets need to be named with a .tsx
extension and some configuration is required in the project's tsconfig.json
:
Add the configuration options for jsx
:
"jsx": "react",
"jsxFactory": "tsx",
Include .tsx
files in the project:
"include": [
"./src/**/*.ts",
"./src/**/*.tsx"
]
Once the project is configured, tsx
can be used in a widget's render
function simply by importing the tsx
function as import { tsx } from '@dojo/widget-core/tsx';
class MyWidgetWithTsx extends WidgetBase<MyProperties> {
protected render(): DNode {
const { clear, properties: { completed, count, activeCount, activeFilter } } = this;
return (
<footer classes={this.classes(css.footer)}>
<span classes={this.classes(css.count)}>
<strong>{`${activeCount}`}</strong>
<span>{`${count}`}</span>
</span>
<TodoFilter activeFilter={activeFilter} />
{ completed ? ( <button onclick={clear} /> ) : ( null ) }
</footer>
);
}
}
Note: Unfortunately tsx
is not directly used within the module so will report as an unused import so would be needed to be ignored by linters.
The WidgetBase
class provides the functionality needed to create Custom Widgets.
This functionality includes caching and widget lifecycle management.
The WidgetBase
class is available from the @dojo/widget-core/WidgetBase
package.
import { WidgetBase } from '@dojo/widget-core/WidgetBase';
All widgets should extend from this class.
The widget's properties lifecycle occurs before its render lifecycle.
Properties passed to the w
function represent the public API for a widget.
The properties lifecycle starts when properties are passed to the widget.
The properties lifecycle is performed in the widgets setProperties
function.
Properties are differenced using DiffType.AUTO
.
Note: If a widget's properties contain complex data structures that you need to diff, then individual control is required using the diffProperty
decorator.
You can control individual property differencing by using the @diffProperty
decorator. Properties with a diffProperty
decorator will be excluded from automatic differencing.
@diffProperty(propertyName, diffType)
can be applied at the class level if you want to use a pre-defined diff function.
@diffProperty('title', DiffType.REFERENCE)
class MyWidget extends WidgetBase<MyProperties> {
}
The following diff functions are provided:
Type | Description |
---|---|
DiffType.ALWAYS |
Always report a property as changed. |
DiffType.AUTO |
Ignore functions, shallow compare objects, and reference compare all other values. |
DiffType.CUSTOM |
Provide a custom diffing function. |
DiffType.IGNORE |
Never report a property as changed. |
DiffType.REFERENCE |
Compare values by reference (old === new ) |
DiffType.SHALLOW |
Treat the values as objects and compare their immediate values by reference. |
DiffType.CUSTOM
is unique in that it takes a third parameter, the diff function. This function has the following signature:
(previousValue: any, newValue: any) => {
changed: boolean;
value: any;
}
You can also provide DiffType.CUSTOM
diff functions by applying a decorator at the method level.
class MyWidget extends WidgetBase<WidgetProperties> {
@diffProperty('foo')
myComplexDiffFunction(previousValue: MyComplexObject, newValue: MyComplexObject) {
return {
changed: true,
value: newValue
};
}
}
For non-decorator environments (Either JavaScript/ES6 or a TypeScript project that does not have the experimental decorators configuration set to true in the tsconfig
), the functions need to be registered in the constructor using the addDecorator
API with diffProperty
as the key.
class MyWidget extends WidgetBase<WidgetProperties> {
constructor() {
super();
diffProperty('foo', DiffType.CUSTOM, this.customFooDiff)(this);
}
customFooDiff(previousProperty: MyComplexObject, newProperty: MyComplexObject) {
}
}
If a property has a custom diff function then that property is excluded from the default property diff.
When diffProperties
has completed, the results are used to update the properties on the widget instance.
If any properties were changed, then the properties:changed
event is emitted. If the new properties do not contain keys from the previous properties, the properties are marked as changed.
// set the initial properties
$widget->setProperties({
foo: true,
bar: true
});
// properties:changed will include the "bar" property
$widget->setProperties({
foo: true
});
Attaching a listener to the event is exposed via a decorator @onPropertiesChanged
.
class MyWidget extends WidgetBase<WidgetProperties> {
@onPropertiesChanged
myPropChangedListener(evt: PropertiesChangeEvent<this, WidgetProperties>) {
// do something
}
}
For non decorator environments the listener can be registered using the onPropertiesChanged
function in the constructor.
class MyWidget extends WidgetBase<WidgetProperties> {
constructor() {
super();
onPropertiesChanged(this.myPropChangedListener)(this);
}
myPropChangedListener(evt: PropertiesChangeEvent<this, WidgetProperties>) {
// do something
}
}
Example event payload
{
type: 'properties:changed',
target: this,
properties: { foo: 'bar', baz: 'qux' },
changedKeyValues: [ 'foo' ]
}
changedKeyValues
represents a list of keys in the properties
key/value pairs where the values have changed.
Finally once all the attached events have been processed, the properties lifecycle is complete and the finalized widget properties are available during the render cycle functions.
Occasionally, in a mixin or base widget class, it my be required to provide logic that needs to be executed before or after a widget's render
call. These lifecycle hooks are supported in WidgetBase
and operate as before and after aspects.
The functionality is provided by the beforeRender
and afterRender
decorators.
Note: Both the beforeRender
and afterRender
functions are executed in the order that they are specified from the super class up to the final class.
The beforeRender
call receives the widget's render
function, properties
and children
and is expected to return a function that satisfies the render
API. The properties
and children
are passed to enable them to be manipulated or decorated prior to the render
being called.
This is the only time in the widget lifecycle that exposes either of these attributes to be manipulated outside of the property system.
class MyBaseClass extends WidgetBase<WidgetProperties> {
@beforeRender
myBeforeRender(renderFunc: () => DNode, properties: any, children: DNode[]): () => DNode {
// decorate/manipulate properties or children.
properties.extraAttribute = 'foo';
// Return or replace the `render` function
return () => {
return v('my-replaced-attribute');
};
}
}
And using the beforeRender
function for non decorator environments:
class MyBaseClass extends WidgetBase<WidgetProperties> {
constructor() {
super();
beforeRender(this.myOtherBeforeRender)(this);
}
myOtherBeforeRender(renderFunc: () => DNode, properties: any, children: DNode[]): () => DNode {
// do something with the result
return renderFunc;
}
}
The afterRender
call receives the returned DNode
s from a widget's render
call, so that the nodes can decorated, manipulated or even swapped.
class MyBaseClass extends WidgetBase<WidgetProperties> {
@afterRender
myAfterRender(result: DNode): DNode {
// do something with the result
return result;
}
}
And using the afterRender
function for non decorator environments:
class MyBaseClass extends WidgetBase<WidgetProperties> {
constructor() {
super();
afterRender(this.myOtherAfterRender)(this);
}
myOtherAfterRender(result: DNode): DNode {
// do something with the result
return result;
}
}
Projector is a term used to describe a widget that will be attached to a DOM element, also known as a root widget.
Any widget can be converted into a projector simply by mixing in the ProjectMixin
mixin.
const MyProjector = ProjectorMixin(MyWidget);
Projectors behave in the same way as any other widget except that they need to be manually instantiated and managed outside of the standard widget lifecycle.
There are 3 ways that a projector widget can be added to the DOM - .append
, .merge
or .replace
, depending on the type of attachment required.
append
- Creates the widget as a child to the projector'sroot
nodemerge
- Merges the widget with the projector'sroot
nodereplace
- Replace the projector'sroot
node with the widget
const MyProjector = ProjectorMixin(MyWidget);
const myProjector = new MyProjector({})
myProjector.append(root);
The recommended pattern for custom event handlers is to declare them on the widget class and reference the function using this
.
Event handlers are most commonly called from render
.
Event handlers can be internal logic encapsulated within a widget or delegate to a function passed into the widget via properties
.
For convenience event handlers are automatically bound to the scope of their enclosing widget.
internally defined handler
class MyWidget extends WidgetBase<WidgetProperties> {
private selected: boolean;
onClick() {
this.selected = !this.selected;
}
render(this: MyWidget): DNode {
return v('div', [
v('input', { type: 'checkbox', onclick: this.onClick }),
v('input', { type: 'text', disabled: this.selected })
]);
}
}
Handler passed via properties
class MyWidget extends WidgetBase<WidgetProperties> {
onClick(): void {
this.properties.mySpecialFunction();
}
}
Binding a function passed to a child widget
import { specialClick } from './mySpecialFunctions';
class MyWidget extends WidgetBase<WidgetProperties> {
render() {
return w(createChildWidget, { onClick: specialClick });
}
}
The widget registry provides the ability to define a label against a WidgetRegistryItem
. A WidgetRegistryItem
is either a WidgetConstructor
a Promise<WidgetConstructor>
or a function that when executed returns a Promise<WidgetConstructor>
.
A global widget registry is exported from the d
module.
import { registry } from '@dojo/widget-core/d';
import MyWidget from './MyWidget';
// registers the widget that will be available immediately
registry.define('my-widget-1', MyWidget);
// registers a promise that is resolving to a widget and will be
// available as soon as the promise resolves.
registry.define('my-widget-2', Promise.resolve(MyWidget));
// registers a function that will be lazily executed the first time the
// 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.resolve(MyWidget));
It is recommended to use the widget registry when defining widgets with w
, to support lazy widget resolution.
Example of registering a function that returns a Promise
that resolves to a widget
.
import load from '@dojo/core/load';
registry.define('my-widget', () => {
return load(require, './MyWidget')
.then(([ MyWidget ]) => MyWidget.default);
});
Working with larger widget structures, it can quickly become tiresome and complex to pass all the required properties down the tree. Needing to pass all required properties also means widgets often need to be aware of properties that are only needed for ensuring their propagation to child widgets.
Dojo 2 provides a mechanism to inject state directly to parts of the widget tree; this is done by defining an Injector
in the registry
and passing a context object that will source the state that is to be injected.
import { Injector, BaseInjector } from '@dojo/widget-core/Injector';
const myStateContext = {
theme: 'solid'
};
registry.define('state', Injector(BaseInjector, myStateContext));
To use the injected state, create a beforeRender
method that returns a render function which creates a w
reference to the registry item like any other widget and pass the properties required by the InjectorProperties
interface.
beforeRender(renderFunc: () => DNode, properties: any, children: any): DNode {
return () => {
return w('state', {
render: renderFunc,
getProperties(context: any, properties: any): any {
return context;
},
properties,
getChildren(context: any, children: DNode[]): DNode[] {
return [];
},
children
});
};
}
This will inject the values of myState
as properties to the widget, as the returned object from getProperties
is mixed over the widget's existing properties.
For convenience, Dojo 2 provides a mixin called Container
that will decorate a widget with the above beforeRender
implementation. Using the Container
mixin enables any view widget to have state injected without coupling the widget to always have state injected. This means the widget can also be used as a normal widget with properties being passed from its parent.
import { MyViewWidget } from './MyViewWidget';
import { Container } from '@dojo/widget-core/Container';
function getProperties(context: any, properties: any): any {
return context;
}
const MyViewWidgetContainer = Container(MyViewWidget, 'state', { getProperties });
Note: that both the getProperties
and getChildren
functions do not need to be provided, if the functions are not defined the default mappers will be used that return an empty object and an empty array respectively.
There may be times when the default BaseInjector
doesn't fully meet your needs. For example if the context contains a reference to an eventable instance, you may want to add an event listener in the Injector
to perform some logic, perhaps invalidate the widget.
To do this the BaseInjector
can be extended easily to add the extra logic required.
interface MyContext {
eventeableInstance: Evented;
bar: number;
}
class MyInjector extends BaseInjector<MyContext> {
constructor(context: MyContext) {
super(context);
const { eventeableInstance } = context;
eventeableInstance.on('change', () => {
this.invalidate();
});
}
protected toInject(): any {
const { eventeableInstance, bar } = this.context;
return {
eventeableInstance,
bar
};
}
}
Widgets are themed using css-modules
and the Themeable
mixin. Each widget must implement a .css file that contains all the css classes that will be used to style it. The baseClasses
object is the css API for the Widget: baseClasses
css classes can be overridden by external themes. Further customization of specific Custom Widget classes can be achieved by passing extraClasses
into the widget.
The Themeable
mixin provides a classes
function that controls the classes to be applied to each node. Classes from the base css
object passed to the classes
function can be themed and overridden. To create fixed classes that cannot be changed, the chained fixed
function can be used.
A base theme is authored using css-modules
and cssnext
. The base theme css
file should be located in a styles
folder within the Widget's package directory.
The typed-css-modules
cli should be used in watch
mode in order to generate typings for TypeScript usage. This is automatically included within the dojo build -w
command from dojo-cli
.
tabPanel
├── createTabPanel.ts
└── styles
└── tabPanel.css
The baseClasses
css must contain a complete set of all of the classes you wish to apply to a widget as all theme and extra classes are limited by the classnames made available here.
Classnames are locally scoped as part of building a Dojo application. A theme key
is generated at build time to locate the themes for each class where a theme is set.
/* tabpanel.css */
.root {
background: red;
}
.tab {
background: blue;
}
To apply baseClasses
a widget must use ThemeableMixin
and the theme
decorator from mixins/Themeable
to register the baseClasses
.
import { WidgetProperties } from '@dojo/widget-core/interfaces';
import { WidgetBase } from '@dojo/widget-core/WidgetBase';
import { ThemeableMixin, theme } from '@dojo/widget-core/mixins/Themeable';
import * as baseClasses from './styles/tabpanel.css';
@theme(baseClasses)
class MyThemeableWidget extends ThemeableMixin(WidgetBase)<WidgetProperties> {
// ...
}
Basic usage:
import { WidgetProperties } from '@dojo/widget-core/interfaces';
import { WidgetBase } from '@dojo/widget-core/WidgetBase';
import { ThemeableMixin, ThemeableMixinProperties, theme } from '@dojo/widget-core/mixins/Themeable';
import * as baseClasses from './styles/tabpanel.css';
interface MyThemeableWidgetProperties extends WidgetProperties, ThemeableMixinProperties;
@theme(baseClasses)
class MyThemeableWidget extends ThemeableMixin(WidgetBase)<MyThemeableWidgetProperties> {
render: function () {
const { root, tab } = baseClasses;
return
v(`ul`, { classes: this.classes(root) }, [
v('li', { classes: this.classes(tab) }, [ 'tab1' ])
// ...
]);
}
}
Themeable widgets include an optional theme
property which can be set to pass in a theme. Theme classes will override baseClasses
. When a theme
property is set or changed, the widgets theme
classes will be regenerated and the widget invalidated such that it is redrawn. Themes are used to apply consistent styling across the widget codebase.
Usage Extending on the previous tabPanel
example.
/* customTheme/tabPanel.css */
.tabs {
background: green;
}
Import the theme and pass it to the widget via its properties
. The theme classes will be automatically mixed into the widget and available via this.classes
.
import * as customTheme from './themes/customTheme.css';
interface MyThemeableWidgetProperties extends WidgetProperties, ThemeableMixinProperties;
class MyThemeableWidget extends ThemeableMixin(WidgetBase)<MyThemeableWidgetProperties> {
render: function () {
// Resulting widget will have green tabs instead of baseTheme red.
return w(createTabPanel, { theme: customTheme });
}
}
The theme can be applied to individual widgets or to a project and property passed down to its children.
As we are using css-modules
to scope widget css classes, the generated class names cannot be used to target specific nodes and apply custom styling to them. Instead you must use the extraClasses
property to pass your generated classes to the widget. This will only effect one instance of a widget and will be applied on top of, rather than instead of, theme classes.
/* tabPaneExtras.css */
.tabs {
font-weight: bold;
}
import * as myExtras from './extras/myExtras.css';
interface MyThemeableWidgetProperties extends WidgetProperties, ThemeableMixinProperties;
class MyThemeableWidget extends ThemeableMixin(WidgetBase)<MyThemeableWidgetProperties> {
render: function () {
// Resulting widget will still have baseTheme red tabs,
// but will have font-weight: bold; applied also
return w(createTabPanel, { extraClasses: myExtras });
}
}
The this.classes
function returns a chained fixed
function that can be used to set non-themeable classes on a node. This allows a widget author to apply classes to a widget that cannot be overridden.
import { WidgetProperties } from '@dojo/widget-core/interfaces';
import { WidgetBase } from '@dojo/widget-core/WidgetBase';
import { ThemeableMixin, ThemeableMixinProperties, theme } from '@dojo/widget-core/mixins/Themeable';
import * as baseClasses from './styles/tabpanel.css';
interface MyThemeableWidgetProperties extends WidgetProperties, ThemeableMixinProperties;
@theme(baseClasses)
class MyThemeableWidget extends ThemeableMixin(WidgetBase)<MyThemeableWidgetProperties> {
render: function () {
const { root, tab } = baseClasses;
return
v(`ul`, { classes: this.classes(root) }, [
v('li', { classes: this.classes().fixed(tab) }, [ 'tab1' ])
// ...
]);
}
}
In the above example, the root
class is still themeable, but the tab
class is applied using .fixed()
so it will not be themeable. The classes passed to .fixed()
can be any string, and unlike the .classes()
parameters, fixed()
css classes do not need to originate from baseClasses
.
Widgets can be internationalized by mixing in @dojo/widget-core/mixins/I18n
.
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. The widget will be invalidated once the locale-specific messages have been loaded.
Each widget can have its own locale by passing a property - properties.locale
.
If no locale is set, then the default locale, as set by @dojo/i18n
, is assumed.
class I18nWidget extends I18nMixin(WidgetBase)<I18nWidgetProperties> {
render: function () {
// Load the "greetings" messages for the current locale. If the locale-specific
// messages have not been loaded yet, then the default messages are returned,
// and the widget will be invalidated once the locale-specific messages have
// loaded.
const messages = this.localizeBundle(greetingsBundle);
return v('div', { title: messages.hello }, [
w(createLabel, {
// Passing a message string to a child widget.
label: messages.purchaseItems
}),
w(createButton, {
// Passing a formatted message string to a child widget.
label: messages.format('itemCount', { count: 2 })
})
]);
}
}
Widgets can be turned into Custom Elements with minimal extra effort.
Just create a CustomElementDescriptor
factory and use the @dojo/cli
build tooling to do the rest of the work,
import { CustomElementDescriptor } from '@dojo/widget-core/customElements';
import MyWidget from './path/to/MyWidget';
export default function createCustomElement(): CustomElementDescriptor {
return {
tagName: 'my-widget',
widgetConstructor: MyWidget,
attributes: [
{
attributeName: 'label'
}
],
events: [
{
propertyName: 'onChange',
name: 'change'
}
]
};
};
By convention, this file should be named createMyWidgetElement.ts
.
To build your custom element, use @dojo/cli,
$ dojo build --element=/path/to/createMyWidget.ts
This will generate the following files:
dist/my-widget/my-widget.html
- HTML import file that includes all widget dependencies. This is the only file you need to import into your HTML page to use your widget.dist/my-widget/my-widget.js
- A compiled version of your widget.dist/my-widget/my-widget.css
- The CSS for your widgetdist/my-widget/widget-core.js
- A shared base widget library. Keeping this separate means that you can include HTML imports for multiple Dojo widgets and the applicartion environment will not re-request this shared file for each widget.
Using your widget would be a simple matter of importing the HTML import:
<!DOCTYPE html>
<html>
<head>
<!-- this will include all JS and CSS used by your widget -->
<link rel="import" href="/path/to/my-widget.html" />
</head>
<body>
<!-- this will actually create your widget -->
<my-widget></my-widget>
</body>
</html>
Your widget will be registered with the browser using the provided tag name. The tag name must have a -
in it.
A widget class that you want wrapped as a custom element.
You can explicitly map widget properties to DOM node attributes with the attributes
array.
{
attributes: [
{
attributeName: 'label'
},
{
attributeName: 'placeholder',
propertyName: 'placeHolder'
},
{
attributeName: 'delete-on-focus',
propertyName: 'deleteOnFocus',
value: value => Boolean(value || 0)
}
]
}
attributeName
- the attribute that will set on the DOM element, e.g.<text-widget label="test" />
.propertyName
- the property on the widget to set; if not set, it defaults to theattributeName
.value
- specify a transformation function on the attribute value. This function should return the value that will be set on the widget's property.
Adding an attribute to the element will also automatically add a corresponding property to the element.
// as an attribute
textWidget.setAttribute('label', 'First Name');
// as a property
textWidget.label = 'First Name';
You can map DOM element properties to widget properties,
{
properties: [
{
propertyName: 'placeholder',
widgetPropertyName: 'placeHolder'
}
]
}
// ...
textWidget.placeholder = 'Enter first name';
propertyName
- name of the property on the DOM elementwidgetPropertyName
- name of the property on the widget; if unspecified,propertyName
is used insteadgetValue
- if specified, will be called with the widget's property value as an argument. The returned value is returned as the DOM element property value.setValue
- if specified, is called with the DOM elements property value. The returned value is used for the widget property's value.
Some widgets have function properties, like events, that need to be exposed to your element. You can use the
events
array to map widget properties to DOM events.
{
events: [
{
propertyName: 'onChange',
eventName: 'change'
}
]
}
This will add a property to onChange
that will emit the change
custom event. You can listen like any other
DOM event,
textWidget.addEventListener('change', function (event) {
// do something
});
Custom logic can be performed after properties/attributes have been defined but before the projector is created. This allows you full control over your widget, allowing you to add custom properties, event handlers, work with child nodes, etc. The initialization function is run from the context of the HTML element.
{
initialization(properties) {
const footer = this.getElementsByTagName('footer');
if (footer) {
properties.footer = footer;
}
const header = this.getElementsByTagName('header');
if (header) {
properties.header = header;
}
}
}
It should be noted that children nodes are removed from the DOM when widget instantiation occurs, and added as children to the widget instance.
These are some of the important principles to keep in mind when creating and using widgets:
- The widget's
__render__
,__setProperties__
,__setChildren__
functions should never be called or overridden - Except for projectors, you should never need to deal directly with widget instances
- Hyperscript should always be written using the @dojo/widget-core
v
helper function - Never set state outside of a widget instance
- Never update
properties
within a widget instance
We appreciate your interest! Please see the Dojo Meta Repository for the Contributing Guidelines and Style Guide.
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
.
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
© 2017 JS Foundation. New BSD license.