| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 } |
| OLD | NEW |