Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 0c22a3b

Browse files
committed
feat(platform): Make angular invoke web_component polyfills for browsers without native web_component implementations.
1 parent 7466489 commit 0c22a3b

15 files changed

+585
-18
lines changed

Diff for: example/pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ dependencies:
55
path: ../
66
browser: any
77
unittest: any
8+
web_components: any
89

910
transformers:
1011
- angular

Diff for: example/web/css/shadow_dom_bracket.css

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
:host {
3+
border-top: 2px solid white;
4+
border-bottom: 2px solid white;
5+
}

Diff for: example/web/css/shadow_dom_components.css

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
:host {
2+
display: block;
3+
margin-top: 5px;
4+
}
5+
6+
/* This must be shimmed with a polymer shim rule */
7+
polyfill-next-selector { content: ':host span:not([:host])'; }
8+
::content span {
9+
background-color: black;
10+
}
11+
12+
my-button::shadow .custom-class {
13+
background-color: rgba(0,0,0, .5);
14+
}
15+
16+
span {
17+
font-weight: bold;
18+
}
19+
20+
.custom-component {
21+
color: white;
22+
padding: 5px;
23+
}
24+
25+
.custom-component a {
26+
color: white;
27+
}
28+
29+
.red {
30+
background-color: #FF1F1F;
31+
}
32+
33+
.green {
34+
background-color: #1BC123;
35+
}
36+
37+
.blue {
38+
background-color: #4D90FE;
39+
}
40+
41+
.grey {
42+
background-color: #E0E0E0;
43+
}

Diff for: example/web/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<li><a href="bouncing_balls.html">bouncing_balls.html</a></li>
66
<li><a href="hello_world.html">hello_world.html</a></li>
77
<li><a href="todo.html">todo.html</a></li>
8+
<li><a href="shadow_dom_components.html">shadow_dom_components.html</a></li>
89
</ul>
910
</body>
1011
</html>

Diff for: example/web/shadow_dom_components.dart

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import 'dart:html';
2+
3+
import 'package:angular/angular.dart';
4+
import 'package:angular/animate/module.dart';
5+
import 'package:angular/application_factory.dart';
6+
import 'package:di/di.dart';
7+
8+
main() {
9+
var app = applicationFactory();
10+
app.modules.add(new Module()
11+
..bind(MyComponent)
12+
..bind(BracketButton));
13+
app.selector("body");
14+
app.run();
15+
}
16+
17+
@Component(
18+
selector: "my-component",
19+
publishAs: "ctrl",
20+
template: """
21+
<div class="custom-component" ng-class="ctrl.color">
22+
<span>Shadow [</span>
23+
<content></content>
24+
<span>]</span>
25+
<a href="#" ng-click="ctrl.on=!ctrl.on"><my-button>
26+
Toggle</my-button></a>
27+
<span ng-if="ctrl.on">off</span>
28+
<span ng-if="!ctrl.on">on</span>
29+
</div>
30+
""",
31+
cssUrl: "/css/shadow_dom_components.css")
32+
class MyComponent {
33+
@NgAttr('color')
34+
String color;
35+
36+
bool on = false;
37+
}
38+
39+
@Component(
40+
selector: "my-button",
41+
template: """<span class="custom-bracket">[[[<content>
42+
</content>]]]</span>""",
43+
cssUrl: "/css/shadow_dom_bracket.css")
44+
class BracketButton {
45+
}

Diff for: example/web/shadow_dom_components.html

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!DOCTYPE html>
2+
3+
<html>
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Web Components test page</title>
8+
<style>
9+
body {
10+
margin: 0;
11+
/* Extra spacing at top and bottom */
12+
padding: 20px 20px 0 20px;
13+
min-height: 100%;
14+
font-family: sans-serif;
15+
}
16+
[ng-cloak] {
17+
display: none;
18+
}
19+
</style>
20+
21+
<script src="packages/web_components/platform.js"></script>
22+
<script src="packages/web_components/dart_support.js"></script>
23+
</head>
24+
25+
<body>
26+
<p ng-if="false">Just waiting for this demo app to load...</p>
27+
<div class="content" ng-cloak>
28+
<h1>Hi</h1>
29+
30+
<p>There should be <strong>three</strong> components here. One red,
31+
one green, one blue:</p>
32+
33+
<my-component color="red">I'm a red component</my-component>
34+
<my-component color="green">I'm a green component</my-component>
35+
<my-component color="blue">I'm a blue component</my-component>
36+
37+
<br />
38+
<my-component color="grey">I'm a
39+
<span>content span</span></my-component>
40+
<br />
41+
<div class="red">I'm just a div with the <code>.red</code> class</div>
42+
<div class="green">I'm just a div with the <code>.green</code> class</div>
43+
<div class="blue">I'm just a div with the <code>.blue</code> class</div>
44+
<br />
45+
<h4>Nesting</h4>
46+
47+
48+
<my-component color="red">
49+
<my-component color="green">
50+
<my-component color="blue">
51+
I'm a blue component.
52+
</my-component>
53+
</my-component>
54+
</my-component>
55+
56+
<my-component color="green">
57+
<my-component color="blue">
58+
<my-component color="red">
59+
I'm a red component.
60+
</my-component>
61+
</my-component>
62+
</my-component>
63+
64+
<my-component color="blue">
65+
<my-component color="red">
66+
<my-component color="green">
67+
I'm a green component.
68+
</my-component>
69+
</my-component>
70+
</my-component>
71+
</div>
72+
<script type="application/dart" src="shadow_dom_components.dart"></script>
73+
<script src="packages/browser/dart.js"></script>
74+
</body>
75+
</html>

Diff for: lib/core/module.dart

+2
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,11 @@ export "package:angular/core_dom/module_internal.dart" show
6060

6161
export "package:angular/core/module_internal.dart" show
6262
CacheStats,
63+
ComponentCssRewriter,
6364
ExceptionHandler,
6465
Interpolate,
6566
VmTurnZone,
67+
WebPlatform,
6668
PrototypeMap,
6769
RootScope,
6870
Scope,

Diff for: lib/core_dom/module_internal.dart

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ library angular.core.dom_internal;
33
import 'dart:async' as async;
44
import 'dart:convert' show JSON;
55
import 'dart:html' as dom;
6+
import 'dart:js' as js;
67

78
import 'package:di/di.dart';
89
import 'package:perf_api/perf_api.dart';
@@ -32,6 +33,7 @@ part 'event_handler.dart';
3233
part 'http.dart';
3334
part 'mustache.dart';
3435
part 'node_cursor.dart';
36+
part 'web_platform.dart';
3537
part 'selector.dart';
3638
part 'shadow_dom_component_factory.dart';
3739
part 'shadowless_shadow_root.dart';
@@ -62,6 +64,8 @@ class CoreDomModule extends Module {
6264
bind(TranscludingComponentFactory);
6365
bind(Content);
6466
bind(ContentPort, toValue: null);
67+
bind(ComponentCssRewriter);
68+
bind(WebPlatform);
6569

6670
bind(Http);
6771
bind(UrlRewriter);

Diff for: lib/core_dom/shadow_dom_component_factory.dart

+87-11
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class ShadowDomComponentFactory implements ComponentFactory {
3030

3131
ShadowDomComponentFactory(this._expando);
3232

33-
final Map<String, async.Future<dom.StyleElement>> _styleElementCache = {};
33+
final Map<_ComponentAssetKey, async.Future<dom.StyleElement>> _styleElementCache = {};
3434

3535
FactoryFn call(dom.Node node, DirectiveRef ref) {
3636
return (Injector injector) {
@@ -42,8 +42,15 @@ class ShadowDomComponentFactory implements ComponentFactory {
4242
DirectiveMap directives = injector.get(DirectiveMap);
4343
NgBaseCss baseCss = injector.get(NgBaseCss);
4444
// This is a bit of a hack since we are returning different type then we are.
45-
var componentFactory = new _ComponentFactory(node, ref.type, component,
46-
injector.get(dom.NodeTreeSanitizer), _expando, baseCss, _styleElementCache);
45+
var componentFactory = new _ComponentFactory(node,
46+
ref.type,
47+
component,
48+
injector.get(dom.NodeTreeSanitizer),
49+
injector.get(WebPlatform),
50+
injector.get(ComponentCssRewriter),
51+
_expando,
52+
baseCss,
53+
_styleElementCache);
4754
var controller = componentFactory.call(injector, scope, viewCache, http, templateCache,
4855
directives);
4956

@@ -67,15 +74,19 @@ class _ComponentFactory implements Function {
6774
final dom.NodeTreeSanitizer treeSanitizer;
6875
final Expando _expando;
6976
final NgBaseCss _baseCss;
70-
final Map<String, async.Future<dom.StyleElement>> _styleElementCache;
77+
final Map<_ComponentAssetKey, async.Future<dom.StyleElement>>
78+
_styleElementCache;
79+
final ComponentCssRewriter componentCssRewriter;
80+
final WebPlatform platform;
7181

7282
dom.ShadowRoot shadowDom;
7383
Scope shadowScope;
7484
Injector shadowInjector;
7585
var controller;
7686

7787
_ComponentFactory(this.element, this.type, this.component, this.treeSanitizer,
78-
this._expando, this._baseCss, this._styleElementCache);
88+
this.platform, this.componentCssRewriter, this._expando,
89+
this._baseCss, this._styleElementCache);
7990

8091
dynamic call(Injector injector, Scope scope,
8192
ViewCache viewCache, Http http, TemplateCache templateCache,
@@ -92,22 +103,57 @@ class _ComponentFactory implements Function {
92103
// better work around is found.
93104
Iterable<async.Future<dom.StyleElement>> cssFutures;
94105
var cssUrls = []..addAll(_baseCss.urls)..addAll(component.cssUrls);
106+
var tag = element.tagName.toLowerCase();
95107
if (cssUrls.isNotEmpty) {
96-
cssFutures = cssUrls.map((cssUrl) => _styleElementCache.putIfAbsent(cssUrl, () =>
108+
cssFutures = cssUrls.map((cssUrl) => _styleElementCache.putIfAbsent(
109+
new _ComponentAssetKey(tag, cssUrl), () =>
97110
http.get(cssUrl, cache: templateCache)
98111
.then((resp) => resp.responseText,
99112
onError: (e) => '/*\n$e\n*/\n')
100-
.then((styleContent) => new dom.StyleElement()..appendText(styleContent))
113+
.then((String css) {
114+
115+
// Shim CSS if required
116+
if (platform.cssShimRequired) {
117+
css = platform.shimCss(css, selector: tag, cssUrl: cssUrl);
118+
}
119+
120+
// If a css rewriter is installed, run the css through a rewriter
121+
var styleElement = new dom.StyleElement()
122+
..appendText(componentCssRewriter(css, selector: tag,
123+
cssUrl: cssUrl));
124+
125+
// ensure there are no invalid tags or modifications
126+
treeSanitizer.sanitizeTree(styleElement);
127+
128+
// If the css shim is required, it means that scoping does not
129+
// work, and adding the style to the head of the document is
130+
// preferrable.
131+
if (platform.cssShimRequired) {
132+
dom.document.head.append(styleElement);
133+
}
134+
135+
return styleElement;
136+
})
101137
)).toList();
102138
} else {
103139
cssFutures = [new async.Future.value(null)];
104140
}
105-
var viewFuture = ComponentFactory._viewFuture(component, viewCache, directives);
141+
142+
var platformViewCache = new PlatformViewCache(viewCache, tag, platform);
143+
144+
var viewFuture = ComponentFactory._viewFuture(component, platformViewCache,
145+
directives);
146+
106147
TemplateLoader templateLoader = new TemplateLoader(
107148
async.Future.wait(cssFutures).then((Iterable<dom.StyleElement> cssList) {
108-
cssList
109-
.where((styleElement) => styleElement != null)
110-
.forEach((styleElement) => shadowDom.append(styleElement.clone(true)));
149+
// This prevents style duplication by only adding css to the shadow
150+
// root if there is a native implementation of shadow dom.
151+
if (!platform.cssShimRequired) {
152+
cssList.where((styleElement) => styleElement != null)
153+
.forEach((styleElement) {
154+
shadowDom.append(styleElement.clone(true));
155+
});
156+
}
111157
if (viewFuture != null) {
112158
return viewFuture.then((ViewFactory viewFactory) {
113159
return (!shadowScope.isAttached) ?
@@ -144,3 +190,33 @@ class _ComponentFactory implements Function {
144190
return shadowInjector;
145191
}
146192
}
193+
194+
class _ComponentAssetKey {
195+
final String tag;
196+
final String assetUrl;
197+
198+
final String _key;
199+
200+
_ComponentAssetKey(String tag, String assetUrl)
201+
: _key = "$tag|$assetUrl",
202+
this.tag = tag,
203+
this.assetUrl = assetUrl;
204+
205+
@override
206+
String toString() => _key;
207+
208+
@override
209+
int get hashCode => _key.hashCode;
210+
211+
bool operator ==(key) =>
212+
key is _ComponentAssetKey
213+
&& tag == key.tag
214+
&& assetUrl == key.assetUrl;
215+
}
216+
217+
@Injectable()
218+
class ComponentCssRewriter {
219+
String call(String css, { String selector, String cssUrl} ) {
220+
return css;
221+
}
222+
}

Diff for: lib/core_dom/view_factory.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class WalkingViewFactory implements ViewFactory {
118118
@Injectable()
119119
class ViewCache {
120120
// _viewFactoryCache is unbounded
121-
final _viewFactoryCache = new LruCache<String, ViewFactory>();
121+
final viewFactoryCache = new LruCache<String, ViewFactory>();
122122
final Http http;
123123
final TemplateCache templateCache;
124124
final Compiler compiler;
@@ -127,12 +127,12 @@ class ViewCache {
127127
ViewCache(this.http, this.templateCache, this.compiler, this.treeSanitizer);
128128

129129
ViewFactory fromHtml(String html, DirectiveMap directives) {
130-
ViewFactory viewFactory = _viewFactoryCache.get(html);
130+
ViewFactory viewFactory = viewFactoryCache.get(html);
131131
if (viewFactory == null) {
132132
var div = new dom.DivElement();
133133
div.setInnerHtml(html, treeSanitizer: treeSanitizer);
134134
viewFactory = compiler(div.nodes, directives);
135-
_viewFactoryCache.put(html, viewFactory);
135+
viewFactoryCache.put(html, viewFactory);
136136
}
137137
return viewFactory;
138138
}

0 commit comments

Comments
 (0)