deact 0.3.2

  • Readme
  • Changelog
  • Example
  • Installing
  • 69

Deact #

A web UI framework inspired by React. An UI is built of components and components are built of DOM elements. Components can be stateless or stateful.

Getting started #

Deact depends on Incremental DOM, an in-place DOM diffing library written in JavaScript. Thus, it is required to load this library.

<html>
    <head>
        ...
        <script src="/packages/incremental_dom_bindings/assets/incremental-dom-min.js"></script>
        ...
    </head>
</html>

The entrypoint of a Deact application is the deact() function. It requires a selector string and a root node. The selector string is used to query a host element from the DOM.

All elements beneath the host element will be deleted and replaced by the provided root node.

A node can be an DOM element, a text or a component.

import 'package:deact/deact.dart';
import 'package:deact/deact_html52.dart';

void main() {
  deact(
    '#root',
    div(children: [txt('Hello World')]),
  );
}

In the example above, a div element with the text Hello World is added beneath the DOM element with the id root.

Components #

If a application becomes more complex, it is advisable to separate the UI into smaller reusable chunks. Here components come into play. A component is a function that returns a node. As as normal Dart function, a component can have parameters to configure the component.

import 'package:deact/deact.dart';
import 'package:deact/deact_html52.dart';

void main() {
  deact(
      '#root',
      fragment([
        coloredText('I am blue.', 'blue'),
        coloredText('I am red.', 'red'),
      ]));
}

DeactNode coloredText(String text, String color) => fc((_) {
      return div(style: 'color: $color', children: [txt(text)]);
    });

In this example a component with the name coloredText is introduced. The name itself is inrelevant for Deact and just helps to give the component a meaningful description. To really create a component, the function fc() has to be used. The only parameter of the fc() function is a builder function that has to return a node. In this case, the component coloredText creates a div element with a text and color.

State #

Local state #

A component can have a state. To access the state of a component, the function state() of the ComponentRenderContext is used. A state has a name and a type.

DeactNode statefulComponent() => fc((ctx) {
      final counter = ctx.state<int>('counter', 0);
      return div(onclick: (_) => counter.set((c) => c + 1), children: [txt('Counter: ${counter.value}')]);
    });

In the example above a state with the name counter and the initial value 0 is created. A state is represented by an instance of the State class. The actual value of a state can be accessed by the getter value. To set a new value for the state, the function set() or the setter value is used. Alternatively, when the value of a state is a more compley type and only parts of it should be updated, the function update()can be used. In both cases, the component and all its children will be rerendered after the state value was updated.

Global state #

State created by the function state()is local to the component. If it is required to share state over multiple components a GlobalStateProvider can be used. A global state provider is a node and thus, it can be placed everywhere in the node hierarchy. Every component beneath a global state provider can access the state of the provider using the function globalState() and read or update it like a local state.

void main() {
  deact(
      '#root',
      globalState<int>(
        name: 'counter',
        initialValue: 0,
        children: [
          incrementor(),
          display(),
        ],
      ));
}

DeactNode incrementor() => fc((ctx) {
      final counter = ctx.globalState<int>('counter');
      return button(onclick: (_) => counter.set((c) => c + 1), children: [txt('Click me to increment to counter')]);
    });

DeactNode display() => fc((ctx) {
      final counter = ctx.globalState<int>('counter');
      return div(children: [txt('Counter: ${counter.value}')]);
    });

Above, a global state with name counter and the initial value 0 is introduced on the top level of the node hierarchy. The components incrementor and display are children of the provider. The component incrementor updates the state and the component display reads the state.

Effects #

An effect is a function, that may be called if

  • a component is added to the node hierarchy
  • a component is rerendered
  • the state of a component has changed

A component can have multiple effects and for each effect, it can be configured on which event it will be triggered.

An effect can have a cleanup function. The cleanup is called depending how the corresponding effect is configured.

If the effect is called when the component was added to the node hierarchy, the cleanup will called, when the component was removed from the hierarchy. If the effect is called on every rerender or in succession to a state change, the cleanup will be called before the effect is called the next time.

DeactNode componentWithEffect() => fc((ctx) {
      final counter = ctx.state<int>('counter', 0);
      ctx.effect('myEffect', () {
          // do something...
          ...

          return () {
            // do some cleanup...
            ...
          };
      }, [counter]);

      ...
    });

In the example above, the effect myEffect is executed every time the state counter has changed. The effect depends on the state counter. The function return by the effect is the cleanup function. The cleanup is executed before the next time, the effect is executed.

If the effect depends on an empty list of states, the effect is only executed, when the component is added to the node hierarchy. The cleanup function is called, when the component is removed from the node hierarchy.

If nullis provided as the list of dependencies, the effect is executed every time the component rerenders. The cleanup is executed before the next time, the effect is executed (but not before the first time the effect is executed).

Examples for the usage of effects are

  • executed HTTP requests
  • acquire and release resources

References #

A reference holds a reference to a value. A reference can be local or global. A reference persists until the component, which has created the reference is removed from the node hierarchy. Changing the reference value will NOT force the component to rerender.

Local references #

A local reference is created by calling the ref() method of the ComponentRenderContext. A reference has a name and an optional initial value. The value of the reference can be accessed by value memeber.

A special way to set the value of a reference is to provide the reference to the ref parameter of an element node.

DeactNode refs() => fc((ctx) {
      final inputRef = ctx.ref<InputElement>('input');

      return fragment([
        button(
          onclick: (_) => inputRef.value.focus(),
          children: [txt('Click me to focus the input element!')],
        ),
        input(ref: inputRef),
      ]);
    });

In this example, a reference to a InputElement is created. The initial value is null. The reference is provided as parameter to the input() function. When the underlying DOM element is created, it is assigned to value of the reference.

Global references #

A global reference is introduced using the function globalRef() which creates an instance of a GloablRefProvider component. All children of this component can access the global reference by calling the gloablRef<T>(String) method of the ComponentRenderContext. The same rules of how to find a global state apply here.

void main() {
  deact(
      '#root',
      globalRef<int>(
        name: 'counter',
        initialValue: 0,
        children: [
          incrementor(),
          display(),
        ],
      ));
}

DeactNode incrementor() => fc((ctx) {
      final counter = ctx.globalRef<int>('counter');
      return button(
        onclick: (_) => counter.value = counter.value + 1,
        children: [txt('Click me to increment to counter')],
      );
    });

DeactNode display() => fc((ctx) {
      final counter = ctx.state<int>('counter', null);
      ctx.effect('init', () {
        // listen to changes of the value of the 'counter' reference
        ctx.globalRef<int>('counter').onChange.listen((c) {
          // update the internal state of the display component.
          // this forces the component to be rerendered. but you
          // do some stuff, that do not force a rerender.
          counter.value = c;
        });
        return null;
      }, dependsOn: []);
      return div(children: [txt('Counter: ${counter.value}')]);
    }, 'display');

As you can see, a reference provices a stream of value change events.

Experimental #

Deact has no stable release yet. Functionality is not yet complete. The API may change and maybe in a breaking way.

Actually, Deact is tested in an internal project.

If you will try Deact: Feedback is welcome!

Changelog #

v0.3.2 #

  • Childs of a node can now be provided as a Iterable instead as only a List.

v0.3.1 #

  • Fix: Setting the attributes selected and checked had not has any effect, if a user interaction has changed the underlying properties

v0.3.0 #

  • BREAKING CHANGE: Renamed Node to DeactNode to avoid name conflicts with the Node class from the dart:html package

v0.2.0 #

  • Added global references (see globalRef()and ComponentRenderContext.globalRef())
  • A Ref now provides a stream of change events
  • BREAKING CHANGE: Renamed globalStateProvider()to globalState()
  • BREAKING CHANGE: Renamed Component to ComponentNode, Element to ElementNode, Text to TextNode and Fragment to FragmentNode to avoid name conflicts with the dart:html package

v0.1.1+2 #

  • Bug fix for references

v0.1.1+1 #

  • Fixed documentation

v0.1.1 #

  • Added references

v0.1.0+1 #

  • Fixed some maintenance and health issues

v0.1.0 #

  • Intial release

example/README.md

Examples #

helloworld #

A very simple Deact application that display Hello World.

component #

Show the usage of components.

state #

Shows the usage of component local state.

globalstate #

Shows the usage of global state.

effects #

Shows the usage of effects.

refs #

Shows the usage of references.

refs #

Shows the usage of global references.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  deact: ^0.3.2

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:deact/deact.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
38
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
69
Learn more about scoring.

We analyzed this package on Apr 7, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.6

Health suggestions

Fix lib/deact.dart. (-0.50 points)

Analysis of lib/deact.dart reported 1 hint:

line 7 col 76: Use lowercase_with_underscores when specifying a library prefix.

Format lib/src/deact/component.dart.

Run dartfmt to format lib/src/deact/component.dart.

Format lib/src/deact/deact_instance.dart.

Run dartfmt to format lib/src/deact/deact_instance.dart.

Fix additional 5 files with analysis or formatting issues.

Additional issues in the following files:

  • lib/src/deact/element.dart (Run dartfmt to format lib/src/deact/element.dart.)
  • lib/src/deact/global_ref_provider.dart (Run dartfmt to format lib/src/deact/global_ref_provider.dart.)
  • lib/src/deact/global_state_provider.dart (Run dartfmt to format lib/src/deact/global_state_provider.dart.)
  • lib/src/deact/render.dart (Run dartfmt to format lib/src/deact/render.dart.)
  • lib/src/deact/tree_location.dart (Run dartfmt to format lib/src/deact/tree_location.dart.)

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.7.0 <3.0.0
incremental_dom_bindings ^1.0.0 1.0.0+1
logging ^0.11.3 0.11.4
Dev dependencies
build_runner ^1.7.0
build_web_compilers ^2.7.0
pedantic ^1.9.0