Introduction

Dojo encourages writing simple, modular components known as widgets which implement single responsibilities out of the wider requirements of an application. Widgets are designed to be composable and reusable across a variety of scenarios, and can be wired together in a reactive manner to fulfill more complex web application requirements.

Widgets describe their intended structural representation by returning virtual nodes from their rendering functions. Dojo’s rendering system then handles ongoing translation of a widget hierarchy’s render output to targeted, efficient DOM updates during application runtime.

FeatureDescription
Reactive by designDojo widgets are designed around core reactive principles to ensure predictable, consistent behavior as state changes propagate through an application.
Encapsulated widgetsCreate independent, encapsulated widgets that can be wired together in a variety of configurations to create complex and beautiful user interfaces.
DOM abstractionsThe framework provides suitable reactive abstractions that mean Dojo applications do not need to interact directly with an imperative DOM.
Efficient renderingDojo’s rendering system can detect state changes within specific subtrees of a widget hierarchy, allowing efficient re-rendering of only the affected portions of an application when an update occurs.
Enterprise-readyCross-cutting application requirements such as internationalization, localization and theming can easily be added to user-created widgets.

Basic usage

Defining a widget

src/widgets/MyWidget.tsx

  1. import { create, tsx } from '@dojo/framework/core/vdom';
  2. const factory = create();
  3. export default factory(function MyWidget() {
  4. return <div>Hello from a Dojo widget!</div>;
  5. });

Specifying widget properties

  • Making widgets more reusable by abstracting out state, configuration and event handling via a typed properties interface
  • Providing middleware to widgets via their create factory
  • Specifying node keys to differentiate between sibling elements of the same type - here, two div elements. This allows the framework to more efficiently target only the relevant elements when updating the DOM as a result of an application state change

src/widgets/Greeter.tsx

  1. import { create, tsx } from '@dojo/framework/core/vdom';
  2. import icache from '@dojo/framework/core/middleware/icache';
  3. const factory = create({ icache }).properties<{
  4. name: string;
  5. onNameChange?(newName: string): void;
  6. }>();
  7. export default factory(function Greeter({ middleware: { icache }, properties }) {
  8. const { name, onNameChange } = properties();
  9. let newName = icache.get<string>('new-name') || '';
  10. return (
  11. <div>
  12. <div key="appBanner">Welcome to a Dojo application!</div>
  13. {name && <div key="nameBanner">Hello, {name}!</div>}
  14. <label for="nameEntry">What's your name?</label>
  15. <input
  16. id="nameEntry"
  17. type="text"
  18. value={newName}
  19. oninput={(e: Event) => {
  20. icache.set('new-name', (e.target as HTMLInputElement).value);
  21. }}
  22. />
  23. <button
  24. onclick={() => {
  25. icache.set('new-name', undefined);
  26. onNameChange && onNameChange(newName);
  27. }}
  28. >
  29. Set my name
  30. </button>
  31. </div>
  32. );
  33. });

Composing widgets

  • Defining a hierarchy of widgets that combine to implement more complex application requirements
  • Providing state and event handler properties to child widgets
  • Making use of icache middleware to manage state and invalidate/re-render affected widgets when the state is changed

src/widgets/NameHandler.tsx

  1. import { create, tsx } from '@dojo/framework/core/vdom';
  2. import icache from '@dojo/framework/core/middleware/icache';
  3. import Greeter from './Greeter';
  4. const factory = create({ icache });
  5. export default factory(function NameHandler({ middleware: { icache } }) {
  6. let currentName = icache.get<string>('current-name') || '';
  7. return (
  8. <Greeter
  9. name={currentName}
  10. onNameChange={(newName) => {
  11. icache.set('current-name', newName);
  12. }}
  13. />
  14. );
  15. });

Rendering to the DOM

  • Using the framework’s renderer to mount a widget hierarchy into the DOM
  • Optionally allowing more control over where Dojo applications appear in a page, for progressive adoption of smaller subcomponents or even to support multiple applications/frameworks within a single page

src/main.tsx

  1. import renderer, { tsx } from '@dojo/framework/core/vdom';
  2. import NameHandler from './widgets/NameHandler';
  3. const r = renderer(() => <NameHandler />);
  4. r.mount();