33*/
44library angular.introspection;
55
6+ import 'dart:async' as async;
67import 'dart:html' as dom;
8+ import 'dart:js' as js;
79import 'package:di/di.dart' ;
8- import 'package:angular/introspection_js .dart' ;
10+ import 'package:angular/animate/module .dart' ;
911import 'package:angular/core/module_internal.dart' ;
1012import 'package:angular/core_dom/module_internal.dart' ;
13+ import 'package:angular/core/static_keys.dart' ;
14+
15+
16+ /**
17+ * A global write only variable which keeps track of objects attached to the
18+ * elements. This is useful for debugging AngularDart application from the
19+ * browser's REPL.
20+ */
21+ var elementExpando = new Expando ('element' );
22+
23+
24+ ElementProbe _findProbeWalkingUp (dom.Node node, [dom.Node ascendUntil]) {
25+ while (node != null && node != ascendUntil) {
26+ var probe = elementExpando[node];
27+ if (probe != null ) return probe;
28+ node = node.parent;
29+ }
30+ return null ;
31+ }
32+
33+
34+ _walkProbesInTree (dom.Node node, Function walker) {
35+ var probe = elementExpando[node];
36+ if (probe == null || walker (probe) != true ) {
37+ for (var child in node.childNodes) {
38+ _walkProbesInTree (child, walker);
39+ }
40+ }
41+ }
42+
43+
44+ ElementProbe _findProbeInTree (dom.Node node, [dom.Node ascendUntil]) {
45+ var probe;
46+ _walkProbesInTree (node, (_probe) {
47+ probe = _probe;
48+ return true ;
49+ });
50+ return (probe != null ) ? probe : _findProbeWalkingUp (node, ascendUntil);
51+ }
52+
53+
54+ List <ElementProbe > _findAllProbesInTree (dom.Node node) {
55+ List <ElementProbe > probes = [];
56+ _walkProbesInTree (node, probes.add);
57+ return probes;
58+ }
59+
1160
1261/**
1362 * Return the [ElementProbe] object for the closest [Element] in the hierarchy.
@@ -21,25 +70,21 @@ import 'package:angular/core_dom/module_internal.dart';
2170 * function is not intended to be called from Angular application.
2271 */
2372ElementProbe ngProbe (nodeOrSelector) {
24- var errorMsg;
25- var node;
2673 if (nodeOrSelector == null ) throw "ngProbe called without node" ;
74+ var node = nodeOrSelector;
2775 if (nodeOrSelector is String ) {
2876 var nodes = ngQuery (dom.document, nodeOrSelector);
29- if (nodes.isNotEmpty) node = nodes.first;
30- errorMsg = "Could not find a probe for the selector '$nodeOrSelector ' nor its parents" ;
31- } else {
32- node = nodeOrSelector;
33- errorMsg = "Could not find a probe for the node '$node ' nor its parents" ;
77+ node = (nodes.isNotEmpty) ? nodes.first : null ;
3478 }
35- while (node != null ) {
36- var probe = elementExpando[node];
37- if (probe != null ) return probe;
38- node = node.parent;
79+ var probe = _findProbeWalkingUp (node);
80+ if (probe != null ) {
81+ return probe;
3982 }
40- throw errorMsg;
83+ var forWhat = (nodeOrSelector is String ) ? "selector" : "node" ;
84+ throw "Could not find a probe for the $forWhat '$nodeOrSelector ' nor its parents" ;
4185}
4286
87+
4388/**
4489 * Return the [Injector] associated with a current [Element] .
4590 *
@@ -79,6 +124,7 @@ List<dom.Element> ngQuery(dom.Node element, String selector,
79124 return list;
80125}
81126
127+
82128/**
83129 * Return a List of directives associated with a current [Element] .
84130 *
@@ -88,3 +134,160 @@ List<dom.Element> ngQuery(dom.Node element, String selector,
88134 */
89135List <Object > ngDirectives (nodeOrSelector) => ngProbe (nodeOrSelector).directives;
90136
137+
138+
139+ js.JsObject _jsProbe (ElementProbe probe) {
140+ return new js.JsObject .jsify ({
141+ "element" : probe.element,
142+ "injector" : _jsInjector (probe.injector),
143+ "scope" : _jsScopeFromProbe (probe),
144+ "directives" : probe.directives.map ((directive) => _jsDirective (directive)),
145+ "bindings" : probe.bindingExpressions,
146+ "models" : probe.modelExpressions
147+ })..['_dart_' ] = probe;
148+ }
149+
150+
151+ js.JsObject _jsInjector (Injector injector) =>
152+ new js.JsObject .jsify ({"get" : injector.get })..['_dart_' ] = injector;
153+
154+
155+ js.JsObject _jsScopeFromProbe (ElementProbe probe) =>
156+ _jsScope (probe.scope, probe.injector.getByKey (SCOPE_STATS_CONFIG_KEY ));
157+
158+
159+ js.JsObject _jsScope (Scope scope, ScopeStatsConfig config) {
160+ return new js.JsObject .jsify ({
161+ "apply" : scope.apply,
162+ "broadcast" : scope.broadcast,
163+ "context" : scope.context,
164+ "destroy" : scope.destroy,
165+ "digest" : scope.rootScope.digest,
166+ "emit" : scope.emit,
167+ "flush" : scope.rootScope.flush,
168+ "get" : (name) => scope.context[name],
169+ "isAttached" : scope.isAttached,
170+ "isDestroyed" : scope.isDestroyed,
171+ "set" : (name, value) => scope.context[name] = value,
172+ "scopeStatsEnable" : () => config.emit = true ,
173+ "scopeStatsDisable" : () => config.emit = false ,
174+ r"$eval" : (expr) => _jsify (scope.eval (expr)),
175+ })..['_dart_' ] = scope;
176+ }
177+
178+
179+ // Helper function to JSify the result of a scope.eval() for simple cases.
180+ _jsify (var obj) {
181+ if (obj is js.JsObject ) {
182+ return obj;
183+ } else if (obj is Iterable ) {
184+ return new js.JsObject .jsify (obj)..['_dart_' ] = obj;
185+ } else {
186+ return obj;
187+ }
188+ }
189+
190+
191+ _jsDirective (directive) => directive;
192+
193+
194+ abstract class _JsObjectProxyable {
195+ js.JsObject _toJsObject ();
196+ }
197+
198+
199+ typedef List <String > _GetExpressionsFromProbe (ElementProbe probe);
200+
201+
202+ /**
203+ * Returns the "$testability service" object for JS / Protractor use.
204+ *
205+ * JS code expects to get a hold of this object in the following way:
206+ *
207+ * // Prereq: There is an "angular" object on window accessible via JS.
208+ * var testability = angular.element(document).injector().get('$testability');
209+ */
210+ class _Testability implements _JsObjectProxyable {
211+ final dom.Node node;
212+ final ElementProbe probe;
213+
214+ _Testability (this .node, this .probe);
215+ _Testability .fromNode (dom.Node node): this (node, _findProbeInTree (node));
216+
217+ notifyWhenNoOutstandingRequests (callback) {
218+ probe.injector.get (VmTurnZone ).run (
219+ () => new async .Timer (Duration .ZERO , callback));
220+ }
221+
222+ /**
223+ * Returns a list of all nodes in the selected tree that have an `ng-model`
224+ * binding specified by the [modelString] . If the optional [exactMatch]
225+ * parameter is provided and true, it restricts the searches to bindings that
226+ * are exact matches for [modelString] .
227+ */
228+ List <dom.Node > findModels (String modelString, [bool exactMatch]) => _findByExpression (
229+ modelString, exactMatch, (ElementProbe probe) => probe.modelExpressions);
230+
231+ /**
232+ * Returns a list of all nodes in the selected tree that have `ng-bind` or
233+ * mustache bindings specified by the [bindingString] . If the optional
234+ * [exactMatch] parameter is provided and true, it restricts the searches to
235+ * bindings that are exact matches for [bindingString] .
236+ */
237+ List <dom.Node > findBindings (String bindingString, [bool exactMatch]) => _findByExpression (
238+ bindingString, exactMatch, (ElementProbe probe) => probe.bindingExpressions);
239+
240+ List <dom.Node > _findByExpression (String query, bool exactMatch, _GetExpressionsFromProbe getExpressions) {
241+ List <ElementProbe > probes = _findAllProbesInTree (node);
242+ if (probes.length == 0 ) {
243+ probes.add (_findProbeWalkingUp (node));
244+ }
245+ List <dom.Node > results = [];
246+ for (ElementProbe probe in probes) {
247+ for (String expression in getExpressions (probe)) {
248+ if (exactMatch == true ? expression == query : expression.indexOf (query) >= 0 ) {
249+ results.add (probe.element);
250+ }
251+ }
252+ }
253+ return results;
254+ }
255+
256+ allowAnimations (bool allowed) {
257+ Animate animate = probe.injector.get (Animate );
258+ bool previous = animate.animationsAllowed;
259+ animate.animationsAllowed = (allowed == true );
260+ return previous;
261+ }
262+
263+ js.JsObject _toJsObject () {
264+ return new js.JsObject .jsify ({
265+ 'allowAnimations' : allowAnimations,
266+ 'findBindings' : (bindingString, [exactMatch]) =>
267+ findBindings (bindingString, exactMatch),
268+ 'findModels' : (modelExpressions, [exactMatch]) =>
269+ findModels (modelExpressions, exactMatch),
270+ 'notifyWhenNoOutstandingRequests' : (callback) =>
271+ notifyWhenNoOutstandingRequests (() => callback.apply ([])),
272+ 'probe' : () => _jsProbe (probe),
273+ 'scope' : () => _jsScopeFromProbe (probe),
274+ 'eval' : (expr) => probe.scope.eval (expr),
275+ 'query' : (String selector, [String containsText]) =>
276+ ngQuery (node, selector, containsText),
277+ })..['_dart_' ] = this ;
278+ }
279+ }
280+
281+
282+ void publishToJavaScript () {
283+ var C = js.context;
284+ C ['ngProbe' ] = (nodeOrSelector) => _jsProbe (ngProbe (nodeOrSelector));
285+ C ['ngInjector' ] = (nodeOrSelector) => _jsInjector (ngInjector (nodeOrSelector));
286+ C ['ngScope' ] = (nodeOrSelector) => _jsScopeFromProbe (ngProbe (nodeOrSelector));
287+ C ['ngQuery' ] = (dom.Node node, String selector, [String containsText]) =>
288+ new js.JsArray .from (ngQuery (node, selector, containsText));
289+ C ['angular' ] = new js.JsObject .jsify ({
290+ 'resumeBootstrap' : ([arg]) {},
291+ 'getTestability' : (node) => new _Testability .fromNode (node)._toJsObject (),
292+ });
293+ }
0 commit comments