| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library observe.src.observable; | 5 library observe.src.observable; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 // TODO(sigmund): figure out how to remove this annotation entirely |
| 10 // Note: ObservableProperty is in this list only for the unusual use case of | 11 // Note: ObservableProperty is in this list only for the unusual use case of |
| 11 // dart2js without deploy tool. The deploy tool (see "transformer.dart") will | 12 // dart2js without deploy tool. The deploy tool (see "transformer.dart") will |
| 12 // add the @reflectable annotation, which makes it work with Polymer's | 13 // add the @reflectable annotation, which makes it work with Polymer's |
| 13 // @published. | 14 // @published. |
| 14 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty], | 15 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty], |
| 15 override: 'observe.src.observable') | 16 override: 'smoke.mirrors') |
| 16 import 'dart:mirrors'; | 17 import 'dart:mirrors' show MirrorsUsed; |
| 17 | 18 import 'package:smoke/smoke.dart' as smoke; |
| 18 import 'package:observe/observe.dart'; | 19 import 'package:observe/observe.dart'; |
| 19 | 20 |
| 20 // Note: this is an internal library so we can import it from tests. | 21 // Note: this is an internal library so we can import it from tests. |
| 21 // TODO(jmesserly): ideally we could import this with a prefix, but it caused | 22 // TODO(jmesserly): ideally we could import this with a prefix, but it caused |
| 22 // strange problems on the VM when I tested out the dirty-checking example | 23 // strange problems on the VM when I tested out the dirty-checking example |
| 23 // above. | 24 // above. |
| 24 import 'dirty_check.dart'; | 25 import 'dirty_check.dart'; |
| 25 | 26 |
| 26 /** | 27 /** |
| 27 * Represents an object with observable properties. This is used by data in | 28 * Represents an object with observable properties. This is used by data in |
| (...skipping 11 matching lines...) Expand all Loading... |
| 39 */ | 40 */ |
| 40 abstract class Observable { | 41 abstract class Observable { |
| 41 /** | 42 /** |
| 42 * Performs dirty checking of objects that inherit from [Observable]. | 43 * Performs dirty checking of objects that inherit from [Observable]. |
| 43 * This scans all observed objects using mirrors and determines if any fields | 44 * This scans all observed objects using mirrors and determines if any fields |
| 44 * have changed. If they have, it delivers the changes for the object. | 45 * have changed. If they have, it delivers the changes for the object. |
| 45 */ | 46 */ |
| 46 static void dirtyCheck() => dirtyCheckObservables(); | 47 static void dirtyCheck() => dirtyCheckObservables(); |
| 47 | 48 |
| 48 StreamController _changes; | 49 StreamController _changes; |
| 49 InstanceMirror _mirror; | |
| 50 | 50 |
| 51 Map<Symbol, Object> _values; | 51 Map<Symbol, Object> _values; |
| 52 List<ChangeRecord> _records; | 52 List<ChangeRecord> _records; |
| 53 | 53 |
| 54 /** | 54 /** |
| 55 * The stream of change records to this object. Records will be delivered | 55 * The stream of change records to this object. Records will be delivered |
| 56 * asynchronously. | 56 * asynchronously. |
| 57 * | 57 * |
| 58 * [deliverChanges] can be called to force synchronous delivery. | 58 * [deliverChanges] can be called to force synchronous delivery. |
| 59 */ | 59 */ |
| 60 Stream<List<ChangeRecord>> get changes { | 60 Stream<List<ChangeRecord>> get changes { |
| 61 if (_changes == null) { | 61 if (_changes == null) { |
| 62 _changes = new StreamController.broadcast(sync: true, | 62 _changes = new StreamController.broadcast(sync: true, |
| 63 onListen: _observed, onCancel: _unobserved); | 63 onListen: _observed, onCancel: _unobserved); |
| 64 } | 64 } |
| 65 return _changes.stream; | 65 return _changes.stream; |
| 66 } | 66 } |
| 67 | 67 |
| 68 /** | 68 /** |
| 69 * True if this object has any observers, and should call | 69 * True if this object has any observers, and should call |
| 70 * [notifyChange] for changes. | 70 * [notifyChange] for changes. |
| 71 */ | 71 */ |
| 72 bool get hasObservers => _changes != null && _changes.hasListener; | 72 bool get hasObservers => _changes != null && _changes.hasListener; |
| 73 | 73 |
| 74 void _observed() { | 74 void _observed() { |
| 75 // Register this object for dirty checking purposes. | 75 // Register this object for dirty checking purposes. |
| 76 registerObservable(this); | 76 registerObservable(this); |
| 77 | 77 |
| 78 var mirror = reflect(this); | |
| 79 var values = new Map<Symbol, Object>(); | 78 var values = new Map<Symbol, Object>(); |
| 80 | 79 |
| 81 // Note: we scan for @observable regardless of whether the base type | 80 // Note: we scan for @observable regardless of whether the base type |
| 82 // actually includes this mixin. While perhaps too inclusive, it lets us | 81 // actually includes this mixin. While perhaps too inclusive, it lets us |
| 83 // avoid complex logic that walks "with" and "implements" clauses. | 82 // avoid complex logic that walks "with" and "implements" clauses. |
| 84 for (var type = mirror.type; type != objectType; type = type.superclass) { | 83 var queryOptions = new smoke.QueryOptions(includeInherited: true, |
| 85 for (var field in type.declarations.values) { | 84 includeProperties: false, withAnnotations: const [ObservableProperty]); |
| 86 if (field is! VariableMirror || | 85 for (var decl in smoke.query(this.runtimeType, queryOptions)) { |
| 87 field.isFinal || | 86 var name = decl.name; |
| 88 field.isStatic || | 87 // Note: since this is a field, getting the value shouldn't execute |
| 89 field.isPrivate) continue; | 88 // user code, so we don't need to worry about errors. |
| 90 | 89 values[name] = smoke.read(this, name); |
| 91 for (var meta in field.metadata) { | |
| 92 if (meta.reflectee is ObservableProperty) { | |
| 93 var name = field.simpleName; | |
| 94 // Note: since this is a field, getting the value shouldn't execute | |
| 95 // user code, so we don't need to worry about errors. | |
| 96 values[name] = mirror.getField(name).reflectee; | |
| 97 break; | |
| 98 } | |
| 99 } | |
| 100 } | |
| 101 } | 90 } |
| 102 | 91 |
| 103 _mirror = mirror; | |
| 104 _values = values; | 92 _values = values; |
| 105 } | 93 } |
| 106 | 94 |
| 107 /** Release data associated with observation. */ | 95 /** Release data associated with observation. */ |
| 108 void _unobserved() { | 96 void _unobserved() { |
| 109 // Note: we don't need to explicitly unregister from the dirty check list. | 97 // Note: we don't need to explicitly unregister from the dirty check list. |
| 110 // This will happen automatically at the next call to dirtyCheck. | 98 // This will happen automatically at the next call to dirtyCheck. |
| 111 if (_values != null) { | 99 if (_values != null) { |
| 112 _mirror = null; | |
| 113 _values = null; | 100 _values = null; |
| 114 } | 101 } |
| 115 } | 102 } |
| 116 | 103 |
| 117 /** | 104 /** |
| 118 * Synchronously deliver pending [changes]. Returns true if any records were | 105 * Synchronously deliver pending [changes]. Returns true if any records were |
| 119 * delivered, otherwise false. | 106 * delivered, otherwise false. |
| 120 */ | 107 */ |
| 121 // TODO(jmesserly): this is a bit different from the ES Harmony version, which | 108 // TODO(jmesserly): this is a bit different from the ES Harmony version, which |
| 122 // allows delivery of changes to a particular observer: | 109 // allows delivery of changes to a particular observer: |
| (...skipping 15 matching lines...) Expand all Loading... |
| 138 // Harmony as well as predictability for app developers. | 125 // Harmony as well as predictability for app developers. |
| 139 bool deliverChanges() { | 126 bool deliverChanges() { |
| 140 if (_values == null || !hasObservers) return false; | 127 if (_values == null || !hasObservers) return false; |
| 141 | 128 |
| 142 // Start with manually notified records (computed properties, etc), | 129 // Start with manually notified records (computed properties, etc), |
| 143 // then scan all fields for additional changes. | 130 // then scan all fields for additional changes. |
| 144 List records = _records; | 131 List records = _records; |
| 145 _records = null; | 132 _records = null; |
| 146 | 133 |
| 147 _values.forEach((name, oldValue) { | 134 _values.forEach((name, oldValue) { |
| 148 var newValue = _mirror.getField(name).reflectee; | 135 var newValue = smoke.read(this, name); |
| 149 if (oldValue != newValue) { | 136 if (oldValue != newValue) { |
| 150 if (records == null) records = []; | 137 if (records == null) records = []; |
| 151 records.add(new PropertyChangeRecord(this, name, oldValue, newValue)); | 138 records.add(new PropertyChangeRecord(this, name, oldValue, newValue)); |
| 152 _values[name] = newValue; | 139 _values[name] = newValue; |
| 153 } | 140 } |
| 154 }); | 141 }); |
| 155 | 142 |
| 156 if (records == null) return false; | 143 if (records == null) return false; |
| 157 | 144 |
| 158 _changes.add(new UnmodifiableListView<ChangeRecord>(records)); | 145 _changes.add(new UnmodifiableListView<ChangeRecord>(records)); |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 195 // public instead? | 182 // public instead? |
| 196 // NOTE: this is not exported publically. | 183 // NOTE: this is not exported publically. |
| 197 notifyPropertyChangeHelper(Observable obj, Symbol field, Object oldValue, | 184 notifyPropertyChangeHelper(Observable obj, Symbol field, Object oldValue, |
| 198 Object newValue) { | 185 Object newValue) { |
| 199 | 186 |
| 200 if (obj.hasObservers && oldValue != newValue) { | 187 if (obj.hasObservers && oldValue != newValue) { |
| 201 obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue)); | 188 obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue)); |
| 202 } | 189 } |
| 203 return newValue; | 190 return newValue; |
| 204 } | 191 } |
| 205 | |
| 206 // NOTE: this is not exported publically. | |
| 207 final objectType = reflectClass(Object); | |
| OLD | NEW |