danschultz / frappe

A Dart package for functional reactive programming
http://pub.dartlang.org/packages/frappe
MIT License
57 stars 13 forks source link

Frappé

Build Status Coverage Status

A functional reactive programming library for Dart. Frappé extends the functionality of Dart's streams, and introduces new concepts like properties/signals.

Why FRP?

UI applications today are highly interactive and data driven. User input can trigger updates to the DOM, playing animations, invoking network requests, and modifying application state. Using the traditional form of event callbacks and modifying state variables can quickly become difficult to write and maintain.

Functional reactive programming (FRP) makes it clearer to define user and system events that cause state changes. For instance, it's easy to define "when a user performs A, do X and Y, then output Z."

When writing reactive code, you'll find yourself focusing more on the dependencies between events for business logic, and less time on their implementation details.

Example

Lets write an auto-complete movie widget with Frappé. The widget has an input field for the movie name, and a list element that displays movies that most closely match the user's input. A working version can be found here.

var searchInput = document.querySelector("#searchInput");
var suggestionsElement = document.querySelector("#suggestions");

var onInput = new EventStream(searchInput.onInput)
    .debounce(new Duration(milliseconds: 250)) // Limit the number of network requests
    .map((event) => event.target.value) // Get the text from the input field
    .distinct(); // Ignore duplicate events with the same text

// Make a network request to get the list of movie suggestions. Because requests
// are asynchronous, they can complete out of order. Use `flatMapLatest` to only
// respond to request for the last text change.
var suggestions = onInput.flatMapLatest((input) => querySuggestions(input));

suggestions.listen((movies) =>
    suggestionsElement.children
        ..clear()
        ..addAll(movies.map((movie) => new LIElement()..text = movie));

// Show "Searching ..." feedback while the request is pending
var isPending = searchInput.onInput.isWaitingOn(suggestions);
isPending.where((value) => value).listen((_) {
  suggestionsElement.children
      ..clear()
      ..add(new DivElement()..text = "Searching ...");
});

Future<List<String>> querySuggestions(String input) {
  // Query some API that returns suggestions for 'input'
}

API

You can explore the full API here.

Reactable

The Reactable class extends from Stream and is inherited by EventStream and Property. Because these classes extend from Dart's Stream, you can pass them directly to other APIs that expect a Stream.

EventStream

An EventStream represents a series of discrete events. They're like a Stream in Dart, but extends its functionality with the methods found on Reactable.

Event streams can be created from a property via Property.asEventStream(), or through one of its constructor methods. If an event stream is created from a property, its first event will be the property's current value.

An EventStream will inherit the behavior of the stream from which it originated. So if an event stream was created from a broadcast stream, it can support multiple subscriptions. Likewise, if an event stream was created from a single-subscription stream, only one subscription can be added to it. Take a look at the article on single-subscription streams vs broadcast streams to learn more about their different behaviors.

Property

A Property represents a value that changes over time. They're similar to event streams, but they remember their current value. Whenever a subscription is added to a property, it will receive the property's current value as its first event.

Properties can be created through one of its constructors, or from an event stream via EventStream.asProperty(). Depending on how the property was created, it may or may not have a starting value. Separate methods are available for creating properties with an initial value, i.e. Property.fromStreamWithInitialValue() and EventStream.asPropertyWithInitialValue(). Properties can support having a null initial value, and is partly the motivation for having separate construction methods.

Internally, properties are implemented as broadcast streams and can receive multiple subscriptions.

If you were to model text input using properties and streams, the individual key strokes would be events, and the resulting text is a property.

Learning More

Definitely take a look at the API documentation, and play around with some of the examples. It's also worth checking out BaconJS and RxJS. They're both mature FRP libraries, and offer some great resourses on the subject.

Running Tests

Tests are run through the Grinder build task. This will run the Dart Analyzer, linter and unit tests.

Features and bugs

Please file feature requests and bugs at the issue tracker.

Contributing

Take a look here on ways to contribute to Frappé.