Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: sdk/lib/html/dart2js/html_dart2js.dart

Issue 17434008: Move PathObserver to mdv_observe package (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/mdv_observe/test/path_observer_test.dart ('k') | sdk/lib/html/dartium/html_dartium.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /// The Dart HTML library. 1 /// The Dart HTML library.
2 library dart.dom.html; 2 library dart.dom.html;
3 3
4 import 'dart:async'; 4 import 'dart:async';
5 import 'dart:collection'; 5 import 'dart:collection';
6 import 'dart:_collection-dev' hide Symbol; 6 import 'dart:_collection-dev' hide Symbol;
7 import 'dart:html_common'; 7 import 'dart:html_common';
8 import 'dart:indexed_db'; 8 import 'dart:indexed_db';
9 import 'dart:isolate'; 9 import 'dart:isolate';
10 import 'dart:json' as json; 10 import 'dart:json' as json;
(...skipping 27450 matching lines...) Expand 10 before | Expand all | Expand 10 after
27461 * Key value used when an implementation is unable to identify another key 27461 * Key value used when an implementation is unable to identify another key
27462 * value, due to either hardware, platform, or software constraints 27462 * value, due to either hardware, platform, or software constraints
27463 */ 27463 */
27464 static const String UNIDENTIFIED = "Unidentified"; 27464 static const String UNIDENTIFIED = "Unidentified";
27465 } 27465 }
27466 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 27466 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
27467 // for details. All rights reserved. Use of this source code is governed by a 27467 // for details. All rights reserved. Use of this source code is governed by a
27468 // BSD-style license that can be found in the LICENSE file. 27468 // BSD-style license that can be found in the LICENSE file.
27469 27469
27470 27470
27471 // This code is inspired by ChangeSummary:
27472 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
27473 // ...which underlies MDV. Since we don't need the functionality of
27474 // ChangeSummary, we just implement what we need for data bindings.
27475 // This allows our implementation to be much simpler.
27476
27477 // TODO(jmesserly): should we make these types stronger, and require
27478 // Observable objects? Currently, it is fine to say something like:
27479 // var path = new PathObserver(123, '');
27480 // print(path.value); // "123"
27481 //
27482 // Furthermore this degenerate case is allowed:
27483 // var path = new PathObserver(123, 'foo.bar.baz.qux');
27484 // print(path.value); // "null"
27485 //
27486 // Here we see that any invalid (i.e. not Observable) value will break the
27487 // path chain without producing an error or exception.
27488 //
27489 // Now the real question: should we do this? For the former case, the behavior
27490 // is correct but we could chose to handle it in the dart:html bindings layer.
27491 // For the latter case, it might be better to throw an error so users can find
27492 // the problem.
27493
27494
27495 /**
27496 * A data-bound path starting from a view-model or model object, for example
27497 * `foo.bar.baz`.
27498 *
27499 * When the [values] stream is being listened to, this will observe changes to
27500 * the object and any intermediate object along the path, and send [values]
27501 * accordingly. When all listeners are unregistered it will stop observing
27502 * the objects.
27503 *
27504 * This class is used to implement [Node.bind] and similar functionality.
27505 */
27506 // TODO(jmesserly): find a better home for this type.
27507 @Experimental
27508 class PathObserver {
27509 /** The object being observed. */
27510 final object;
27511
27512 /** The path string. */
27513 final String path;
27514
27515 /** True if the path is valid, otherwise false. */
27516 final bool _isValid;
27517
27518 // TODO(jmesserly): same issue here as ObservableMixin: is there an easier
27519 // way to get a broadcast stream?
27520 StreamController _values;
27521 Stream _valueStream;
27522
27523 _PropertyObserver _observer, _lastObserver;
27524
27525 Object _lastValue;
27526 bool _scheduled = false;
27527
27528 /**
27529 * Observes [path] on [object] for changes. This returns an object that can be
27530 * used to get the changes and get/set the value at this path.
27531 * See [PathObserver.values] and [PathObserver.value].
27532 */
27533 PathObserver(this.object, String path)
27534 : path = path, _isValid = _isPathValid(path) {
27535
27536 // TODO(jmesserly): if the path is empty, or the object is! Observable, we
27537 // can optimize the PathObserver to be more lightweight.
27538
27539 _values = new StreamController.broadcast(sync: true,
27540 onListen: _observe,
27541 onCancel: _unobserve);
27542
27543 if (_isValid) {
27544 var segments = [];
27545 for (var segment in path.trim().split('.')) {
27546 if (segment == '') continue;
27547 var index = int.parse(segment, onError: (_) {});
27548 segments.add(index != null ? index : new Symbol(segment));
27549 }
27550
27551 // Create the property observer linked list.
27552 // Note that the structure of a path can't change after it is initially
27553 // constructed, even though the objects along the path can change.
27554 for (int i = segments.length - 1; i >= 0; i--) {
27555 _observer = new _PropertyObserver(this, segments[i], _observer);
27556 if (_lastObserver == null) _lastObserver = _observer;
27557 }
27558 }
27559 }
27560
27561 // TODO(jmesserly): we could try adding the first value to the stream, but
27562 // that delivers the first record async.
27563 /**
27564 * Listens to the stream, and invokes the [callback] immediately with the
27565 * current [value]. This is useful for bindings, which want to be up-to-date
27566 * immediately.
27567 */
27568 StreamSubscription bindSync(void callback(value)) {
27569 var result = values.listen(callback);
27570 callback(value);
27571 return result;
27572 }
27573
27574 // TODO(jmesserly): should this be a change record with the old value?
27575 // TODO(jmesserly): should this be a broadcast stream? We only need
27576 // single-subscription in the bindings system, so single sub saves overhead.
27577 /**
27578 * Gets the stream of values that were observed at this path.
27579 * This returns a single-subscription stream.
27580 */
27581 Stream get values => _values.stream;
27582
27583 /** Force synchronous delivery of [values]. */
27584 void _deliverValues() {
27585 _scheduled = false;
27586
27587 var newValue = value;
27588 if (!identical(_lastValue, newValue)) {
27589 _values.add(newValue);
27590 _lastValue = newValue;
27591 }
27592 }
27593
27594 void _observe() {
27595 if (_observer != null) {
27596 _lastValue = value;
27597 _observer.observe();
27598 }
27599 }
27600
27601 void _unobserve() {
27602 if (_observer != null) _observer.unobserve();
27603 }
27604
27605 void _notifyChange() {
27606 if (_scheduled) return;
27607 _scheduled = true;
27608
27609 // TODO(jmesserly): should we have a guarenteed order with respect to other
27610 // paths? If so, we could implement this fairly easily by sorting instances
27611 // of this class by birth order before delivery.
27612 queueChangeRecords(_deliverValues);
27613 }
27614
27615 /** Gets the last reported value at this path. */
27616 get value {
27617 if (!_isValid) return null;
27618 if (_observer == null) return object;
27619 _observer.ensureValue(object);
27620 return _lastObserver.value;
27621 }
27622
27623 /** Sets the value at this path. */
27624 void set value(Object value) {
27625 // TODO(jmesserly): throw if property cannot be set?
27626 // MDV seems tolerant of these error.
27627 if (_observer == null || !_isValid) return;
27628 _observer.ensureValue(object);
27629 var last = _lastObserver;
27630 if (_setObjectProperty(last._object, last._property, value)) {
27631 // Technically, this would get updated asynchronously via a change record.
27632 // However, it is nice if calling the getter will yield the same value
27633 // that was just set. So we use this opportunity to update our cache.
27634 last.value = value;
27635 }
27636 }
27637 }
27638
27639 // TODO(jmesserly): these should go away in favor of mirrors!
27640 _getObjectProperty(object, property) {
27641 if (object is List && property is int) {
27642 if (property >= 0 && property < object.length) {
27643 return object[property];
27644 } else {
27645 return null;
27646 }
27647 }
27648
27649 // TODO(jmesserly): what about length?
27650 if (object is Map) return object[property];
27651
27652 if (object is Observable) return object.getValueWorkaround(property);
27653
27654 return null;
27655 }
27656
27657 bool _setObjectProperty(object, property, value) {
27658 if (object is List && property is int) {
27659 object[property] = value;
27660 } else if (object is Map) {
27661 object[property] = value;
27662 } else if (object is Observable) {
27663 (object as Observable).setValueWorkaround(property, value);
27664 } else {
27665 return false;
27666 }
27667 return true;
27668 }
27669
27670
27671 class _PropertyObserver {
27672 final PathObserver _path;
27673 final _property;
27674 final _PropertyObserver _next;
27675
27676 // TODO(jmesserly): would be nice not to store both of these.
27677 Object _object;
27678 Object _value;
27679 StreamSubscription _sub;
27680
27681 _PropertyObserver(this._path, this._property, this._next);
27682
27683 get value => _value;
27684
27685 void set value(Object newValue) {
27686 _value = newValue;
27687 if (_next != null) {
27688 if (_sub != null) _next.unobserve();
27689 _next.ensureValue(_value);
27690 if (_sub != null) _next.observe();
27691 }
27692 }
27693
27694 void ensureValue(object) {
27695 // If we're observing, values should be up to date already.
27696 if (_sub != null) return;
27697
27698 _object = object;
27699 value = _getObjectProperty(object, _property);
27700 }
27701
27702 void observe() {
27703 if (_object is Observable) {
27704 assert(_sub == null);
27705 _sub = (_object as Observable).changes.listen(_onChange);
27706 }
27707 if (_next != null) _next.observe();
27708 }
27709
27710 void unobserve() {
27711 if (_sub == null) return;
27712
27713 _sub.cancel();
27714 _sub = null;
27715 if (_next != null) _next.unobserve();
27716 }
27717
27718 void _onChange(List<ChangeRecord> changes) {
27719 for (var change in changes) {
27720 // TODO(jmesserly): what to do about "new Symbol" here?
27721 // Ideally this would only preserve names if the user has opted in to
27722 // them being preserved.
27723 // TODO(jmesserly): should we drop observable maps with String keys?
27724 // If so then we only need one check here.
27725 if (change.changes(_property)) {
27726 value = _getObjectProperty(_object, _property);
27727 _path._notifyChange();
27728 return;
27729 }
27730 }
27731 }
27732 }
27733
27734 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
27735
27736 const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*';
27737 final _pathRegExp = new RegExp('^'
27738 '(?:#?' + _pathIndentPart + ')?'
27739 '(?:'
27740 '(?:\\.' + _pathIndentPart + ')'
27741 ')*'
27742 r'$', caseSensitive: false);
27743
27744 final _spacesRegExp = new RegExp(r'\s');
27745
27746 bool _isPathValid(String s) {
27747 s = s.replaceAll(_spacesRegExp, '');
27748
27749 if (s == '') return true;
27750 if (s[0] == '.') return false;
27751 return _pathRegExp.hasMatch(s);
27752 }
27753 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
27754 // for details. All rights reserved. Use of this source code is governed by a
27755 // BSD-style license that can be found in the LICENSE file.
27756
27757
27758 /** 27471 /**
27759 * A utility class for representing two-dimensional positions. 27472 * A utility class for representing two-dimensional positions.
27760 */ 27473 */
27761 class Point { 27474 class Point {
27762 final num x; 27475 final num x;
27763 final num y; 27476 final num y;
27764 27477
27765 const Point([num x = 0, num y = 0]): x = x, y = y; 27478 const Point([num x = 0, num y = 0]): x = x, y = y;
27766 27479
27767 String toString() => '($x, $y)'; 27480 String toString() => '($x, $y)';
(...skipping 2116 matching lines...) Expand 10 before | Expand all | Expand 10 after
29884 _position = nextPosition; 29597 _position = nextPosition;
29885 return true; 29598 return true;
29886 } 29599 }
29887 _current = null; 29600 _current = null;
29888 _position = _array.length; 29601 _position = _array.length;
29889 return false; 29602 return false;
29890 } 29603 }
29891 29604
29892 T get current => _current; 29605 T get current => _current;
29893 } 29606 }
OLDNEW
« no previous file with comments | « pkg/mdv_observe/test/path_observer_test.dart ('k') | sdk/lib/html/dartium/html_dartium.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698