CompoundBinding class
Model-Driven Views contains a helper object which is useful for the implementation of a Custom Syntax.
var binding = new CompoundBinding((values) {
var combinedValue;
// compute combinedValue based on the current values which are provided
return combinedValue;
});
binding.bind('name1', obj1, path1);
binding.bind('name2', obj2, path2);
//...
binding.bind('nameN', objN, pathN);
CompoundBinding is an object which knows how to listen to multiple path
values (registered via bind) and invoke its combinator when one or more
of the values have changed and set its value property to the return value
of the function. When any value has changed, all current values are provided
to the combinator in the single values
argument.
class CompoundBinding extends ObservableBase { CompoundBindingCombinator _combinator; // TODO(jmesserly): ideally these would be String keys, but sometimes we // use integers. Map<dynamic, StreamSubscription> _bindings = new Map(); Map _values = new Map(); bool _scheduled = false; bool _disposed = false; Object _value; CompoundBinding([CompoundBindingCombinator combinator]) { // TODO(jmesserly): this is a tweak to the original code, it seemed to me // that passing the combinator to the constructor should be equivalent to // setting it via the property. // I also added a null check to the combinator setter. this.combinator = combinator; } CompoundBindingCombinator get combinator => _combinator; set combinator(CompoundBindingCombinator combinator) { _combinator = combinator; if (combinator != null) _scheduleResolve(); } static const _VALUE = const Symbol('value'); get value => _value; void set value(newValue) { _value = notifyPropertyChange(_VALUE, _value, newValue); } void bind(name, model, String path) { unbind(name); _bindings[name] = new PathObserver(model, path).bindSync((value) { _values[name] = value; _scheduleResolve(); }); } void unbind(name, {bool suppressResolve: false}) { var binding = _bindings.remove(name); if (binding == null) return; binding.cancel(); _values.remove(name); if (!suppressResolve) _scheduleResolve(); } // TODO(rafaelw): Is this the right processing model? // TODO(rafaelw): Consider having a seperate ChangeSummary for // CompoundBindings so to excess dirtyChecks. void _scheduleResolve() { if (_scheduled) return; _scheduled = true; queueChangeRecords(resolve); } void resolve() { if (_disposed) return; _scheduled = false; if (_combinator == null) { throw new StateError( 'CompoundBinding attempted to resolve without a combinator'); } value = _combinator(_values); } void dispose() { for (var binding in _bindings.values) { binding.cancel(); } _bindings.clear(); _values.clear(); _disposed = true; value = null; } }
Extends
ObservableBase > CompoundBinding
Constructors
new CompoundBinding([CompoundBindingCombinator combinator]) #
CompoundBinding([CompoundBindingCombinator combinator]) { // TODO(jmesserly): this is a tweak to the original code, it seemed to me // that passing the combinator to the constructor should be equivalent to // setting it via the property. // I also added a null check to the combinator setter. this.combinator = combinator; }
Properties
final Stream<List<ChangeRecord>> changes #
The stream of change records to this object.
Changes should be delivered in asynchronous batches by calling queueChangeRecords.
deliverChangeRecords can be called to force delivery.
Stream<List<ChangeRecord>> get changes { if (_broadcastController == null) { _broadcastController = new StreamController<List<ChangeRecord>>.broadcast(sync: true); } return _broadcastController.stream; }
CompoundBindingCombinator combinator #
CompoundBindingCombinator get combinator => _combinator;
set combinator(CompoundBindingCombinator combinator) { _combinator = combinator; if (combinator != null) _scheduleResolve(); }
final bool hasObservers #
True if this object has any observers, and should call notifyPropertyChange for changes.
bool get hasObservers => _broadcastController != null && _broadcastController.hasListener;
var value #
get value => _value;
void set value(newValue) { _value = notifyPropertyChange(_VALUE, _value, newValue); }
Methods
void bind(name, model, String path) #
void bind(name, model, String path) { unbind(name); _bindings[name] = new PathObserver(model, path).bindSync((value) { _values[name] = value; _scheduleResolve(); }); }
void dispose() #
void dispose() { for (var binding in _bindings.values) { binding.cancel(); } _bindings.clear(); _values.clear(); _disposed = true; value = null; }
void notifyChange(ChangeRecord record) #
Notify observers of a change. For most objects notifyPropertyChange is more convenient, but collections sometimes deliver other types of changes such as a ListChangeRecord.
void notifyChange(ChangeRecord record) { if (!hasObservers) return; if (_changes == null) { _changes = []; queueChangeRecords(_deliverChanges); } _changes.add(record); }
dynamic notifyPropertyChange(Symbol field, Object oldValue, Object newValue) #
Notify that the field name
of this object has been changed.
The oldValue and newValue are also recorded. If the two values are identical, no change will be recorded.
For convenience this returns newValue. This makes it easy to use in a setter:
var _myField;
get myField => _myField;
set myField(value) {
_myField = notifyPropertyChange(
const Symbol('myField'), _myField, value);
}
notifyPropertyChange(Symbol field, Object oldValue, Object newValue) { if (hasObservers && !identical(oldValue, newValue)) { notifyChange(new PropertyChangeRecord(field)); } return newValue; }
void resolve() #
void resolve() { if (_disposed) return; _scheduled = false; if (_combinator == null) { throw new StateError( 'CompoundBinding attempted to resolve without a combinator'); } value = _combinator(_values); }
void unbind(name, {bool suppressResolve: false}) #
void unbind(name, {bool suppressResolve: false}) { var binding = _bindings.remove(name); if (binding == null) return; binding.cancel(); _values.remove(name); if (!suppressResolve) _scheduleResolve(); }