Permalink
Browse files

feature: Flux-Pattern for MDL/Dart

  • Loading branch information...
MikeMitterer committed Nov 30, 2015
1 parent 8273d61 commit f4750ba0d2b6671f56d8743130a6412abbf19ed0
View
@@ -15,3 +15,4 @@ example/**/*.css
syncsite
site/downloads/
doc/
View
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2015, Michael Mitterer (office@mikemitterer.at),
* IT-Consulting and Development Limited.
*
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// MdlFlux implements unidirectional flow in contrast to popular frameworks such as Angular or Ember.
/// Even though two-directional bindings can be convenient, they come with a cost.
/// It can be hard to deduce what's going on and why.
///
/// When we trigger the action, the dispatcher will get notified.
/// The dispatcher will be able to deal with possible store dependencies.
/// It is possible that certain action needs to happen before another. Dispatcher allows us to achieve this.
///
/// At the simplest level, actions can just pass the message to dispatcher as is.
/// They can also trigger asynchronous queries and hit dispatcher based on the result eventually.
/// This allows us to deal with received data and possible errors.
///
/// Once the dispatcher has dealt with the action, stores that are listening to it get triggered.
/// As a result, it will be able to update its internal state.
/// After doing this it will notify possible listeners of the new state.
///
/// This completes the basic unidirectional, yet linear, process flow of MdlFlux.
/// Usually, though, the unidirectional process has a cyclical flow and it doesn't necessarily end.
/// The following diagram illustrates a more common flow.
///
/// It is the same idea again, but with the addition of a returning cycle.
/// Eventually the components depending our our store data become refreshed through this looping process.
///
/// **More infos about flux:**
///
/// * [Flux: An Application Architecture for React](https://facebook.github.io/react/blog/2014/05/06/flux.html)
///
/// * [React and Flux](http://survivejs.com/webpack_react/react_and_flux/)
library mdlflux;
import 'dart:async';
import 'package:di/di.dart' as di;
import 'package:logging/logging.dart';
import 'package:validate/validate.dart';
part "src/flux/interfaces.dart";
part "src/flux/action.dart";
part "src/flux/ActionBusImpl.dart";
part "src/flux/DataStore.dart";
part "src/flux/Dispatcher.dart";
/// Stock-Action that is emitted by the [Dispatcher.emitChange]-Function
const Action UpdateView = const Action(ActionType.Signal, const ActionName("mdl.mdlflux.datastore.update"));
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2015, Michael Mitterer (office@mikemitterer.at),
* IT-Consulting and Development Limited.
*
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
part of mdlflux;
/// Implements the [ActionBus]-interface via Streams
///
/// There is ONE and really one ActionBus per system / application
///
/// final ActionBus actionbus = new ActionBus();
///
/// actionbus.on(TestSignal.NAME).listen((final Action action) {
/// _logger.info("ActionTest: ${action.name}");
/// });
///
/// actionbus.fire(const TestSignal());
///
class ActionBusImpl implements ActionBus {
StreamController<Action> _controller;
Stream<Action> _stream;
static ActionBusImpl _actionbus;
factory ActionBusImpl() {
return _actionbus ?? (_actionbus = new ActionBusImpl._internal());
}
/// Sends [Action] into the global [ActionBus]
@override
void fire(final Action action) {
if(_controller != null && _controller.hasListener && !_controller.isClosed) {
_controller.add(action);
}
}
/// Receives [ActionName] from the global [ActionBus]
@override
Stream<Action> on(final ActionName actionname) {
return _stream.where((final Action action) => (action.actionname == actionname));
}
// -- private -------------------------------------------------------------
/// Private constructor - used by the "singleton" factory
ActionBusImpl._internal() {
_controller = new StreamController<Action>.broadcast(onCancel: () => _controller = null);
_stream = _controller.stream.asBroadcastStream();
}
}
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2015, Michael Mitterer (office@mikemitterer.at),
* IT-Consulting and Development Limited.
*
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
part of mdlflux;
/// Received by a concrete implementation of [DataStore] if something has changed in the [DataStore]
class DataStoreChangedEvent<T extends Action> {
final T data;
DataStoreChangedEvent(this.data);
bool get hasParam => (data != null);
bool get hasNoParam => !hasParam;
}
/// The [DataStore] are responsible for managing business logic and data.
///
/// They're akin to models or collections in MVC systems,
/// but stores may manage more than a single piece of data or a single collection,
/// as they are responsible for a domain of the application.
///
/// A [DataStore] has per definition not setters!
///
/// Usage I: (Component)
///
/// abstract class MyComponentStore extends DataStore {
/// Optional<Device> get activeDevice;
/// bool hasID(final String id);
/// }
///
///
/// class MyComponent {
/// final MyComponentStore _store;
///
/// MyComponent.fromElement(final dom.HtmlElement element,final di.Injector injector)
/// : super(element,injector), _store = injector.get(MyComponentStore) {
/// _init();
/// }
///
/// void _init() {
/// render().then((_) => _bindSignals());
/// }
///
/// void _bindSignals() {
/// _store.onChange.listen( (_) {
/// // UPDATE your VIEW!
/// });
/// }
///
/// void onClick() {
/// _store.fire(const ActivateSomething());
/// }
/// }
///
abstract class DataStore {
StreamController<DataStoreChangedEvent<Action>> _onChange;
Stream<DataStoreChangedEvent<Action>> get onChange {
if (_onChange == null) {
_onChange =
new StreamController<DataStoreChangedEvent<Action>>.broadcast(onCancel: () => _onChange = null);
}
return _onChange.stream;
}
void fire(final Action action);
}
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2015, Michael Mitterer (office@mikemitterer.at),
* IT-Consulting and Development Limited.
*
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
part of mdlflux;
/// The [Dispatcher] is the central hub that manages all data flow in a MDL(Flux) application.
///
/// The [Dispatcher] has very little or no real intelligence of its own.
/// It is a simple mechanism for distributing change-Events (mainly via [UpdateView]-Actions)
/// actions to the stores.
///
/// Each store registers itself and provides an onChange-Listener.
///
/// class TestDataStore extends Dispatcher implements TestDataStoreInterface {
///
/// String _name = "Mike";
///
/// String get name => _name;
///
/// TestDataStore(final ActionBus actionbus) : super(actionbus);
///
/// void set name(final String value) {
/// _name = value;
/// emitChange();
/// }
/// }
///
/// /// Interface defined by a component
/// abstract class TestDataStoreInterface extends DataStore {
/// String get name;
/// }
///
/// class MyComponent {
/// TestDataStoreInterface _store;
///
/// /// [testCallback] is just for testing the _actionbus-response
/// MyComponent(this._store, void testCallback(final String value) ) {
/// _store.onChange.listen( (_) {
/// testCallback(_store.name);
/// });
/// }
/// }
///
/// void main() {
/// final ActionBus actionbus = new ActionBus();
///
/// final TestDataStore datastore = new TestDataStore(actionbus);
///
/// final MyComponent component = new MyComponent(datastore, (final String value ) {
/// expect(value,"Dart");
/// });
/// }
///
abstract class Dispatcher extends DataStore {
final ActionBus _actionbus;
Dispatcher(this._actionbus) {
Validate.notNull(_actionbus);
}
/// Informs the coupled [DataStore]s about the change
void emitChange({ final Action action: UpdateView }) {
if (_onChange != null && _onChange.hasListener && !_onChange.isClosed) {
_onChange.add(new DataStoreChangedEvent<Action>(action));
}
}
/// Fire an [Action] to the global [ActionBus]
@override
void fire(final Action action) => _actionbus.fire(action);
/// Listen on [Action]s on the global [ActionBus]
Stream<Action> on(final ActionName actionname) {
return _actionbus.on(actionname);
}
}
View
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2015, Michael Mitterer (office@mikemitterer.at),
* IT-Consulting and Development Limited.
*
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
part of mdlflux;
/// Gives you more infos about the action you receive
///
/// Signal is the simplest form of an action. It contains no data
/// Sample:
/// const BasicAction UpdateView = const BasicAction(
/// ActionType.Signal, const ActionName("mdl.mdlflux.updateview"));
///
/// Data: Action has some data
/// Json: Action has some JSON-Data
///
enum ActionType { Signal, Data, Json }
/// Strong-Typed [ActionName]
///
/// class ActivateDevice extends DataAction<String> {
/// static const ActionName NAME = const ActionName("sample.components.ActivateDevice");
/// ActivateDevice(final String id) : super(NAME,id);
/// }
///
class ActionName {
final String name;
const ActionName(this.name);
}
/// Simples form of an Action
///
/// class DataLoadedAction extends Signal {
/// static const ActionName NAME = const ActionName("sample.datastore.DataLoadedAction");
/// DataLoadedAction(): super(ActionType.Signal,NAME);
/// }
class Action {
final ActionType type;
final ActionName actionname;
const Action(this.type, this.actionname);
String get name => actionname.name;
bool get hasData => false;
bool get isJson => false;
}
/// This [Action] carries a data-package
///
/// class ActivateDevice extends DataAction<String> {
/// static const ActionName NAME = const ActionName("sample.components.ActivateDevice");
/// ActivateDevice(final String id) : super(NAME,id);
/// }
abstract class DataAction<T> extends Action {
final T data;
const DataAction(final ActionName name,this.data) : super(ActionType.Data,name);
@override
bool get hasData => data != null;
}
Oops, something went wrong.

0 comments on commit f4750ba

Please sign in to comment.