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

Commit

Permalink
feat(web components): Support custom events for element property binding
Browse files Browse the repository at this point in the history
Closes #1449
Closes #1453
  • Loading branch information
jbdeboer authored and vsavkin committed Sep 15, 2014
1 parent 059b76e commit 94c3522
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 17 deletions.
36 changes: 28 additions & 8 deletions lib/core/annotation_src.dart
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,28 @@ abstract class Directive {
*/
final List<String> exportExpressions;

/**
* Event names to listen to during Web Component two-way binding.
*
* To support web components efficiently, Angular only reads element
* bindings when specific events are fired. By default, Angular listens
* to 'change'. Adding events names to this listen will cause Angular
* to listen to those events instead.
*
* The name is intentionally long: this should be rarely used and therefore
* it is important that it is self-documenting.
*/
final List<String> updateBoundElementPropertiesOnEvents;

const Directive({
this.selector,
this.children,
this.visibility,
this.module,
this.map: const {},
this.exportExpressions: const [],
this.exportExpressionAttrs: const []
this.exportExpressionAttrs: const [],
this.updateBoundElementPropertiesOnEvents
});

toString() => selector;
Expand Down Expand Up @@ -282,15 +296,17 @@ class Component extends Directive {
exportExpressions,
exportExpressionAttrs,
this.useShadowDom,
this.useNgBaseCss: true})
: _cssUrls = cssUrl,
this.useNgBaseCss: true,
updateBoundElementPropertiesOnEvents
}) : _cssUrls = cssUrl,
super(selector: selector,
children: Directive.COMPILE_CHILDREN,
visibility: visibility,
map: map,
module: module,
exportExpressions: exportExpressions,
exportExpressionAttrs: exportExpressionAttrs);
exportExpressionAttrs: exportExpressionAttrs,
updateBoundElementPropertiesOnEvents: updateBoundElementPropertiesOnEvents);

List<String> get cssUrls => _cssUrls == null ?
const [] :
Expand All @@ -309,7 +325,8 @@ class Component extends Directive {
exportExpressions: exportExpressions,
exportExpressionAttrs: exportExpressionAttrs,
useShadowDom: useShadowDom,
useNgBaseCss: useNgBaseCss);
useNgBaseCss: useNgBaseCss,
updateBoundElementPropertiesOnEvents: updateBoundElementPropertiesOnEvents);
}

/**
Expand All @@ -332,14 +349,16 @@ class Decorator extends Directive {
DirectiveBinderFn module,
visibility,
exportExpressions,
exportExpressionAttrs})
exportExpressionAttrs,
updateBoundElementPropertiesOnEvents})
: super(selector: selector,
children: children,
visibility: visibility,
map: map,
module: module,
exportExpressions: exportExpressions,
exportExpressionAttrs: exportExpressionAttrs);
exportExpressionAttrs: exportExpressionAttrs,
updateBoundElementPropertiesOnEvents: updateBoundElementPropertiesOnEvents);

Directive _cloneWithNewMap(newMap) =>
new Decorator(
Expand All @@ -349,7 +368,8 @@ class Decorator extends Directive {
selector: selector,
visibility: visibility,
exportExpressions: exportExpressions,
exportExpressionAttrs: exportExpressionAttrs);
exportExpressionAttrs: exportExpressionAttrs,
updateBoundElementPropertiesOnEvents: updateBoundElementPropertiesOnEvents);
}

/**
Expand Down
26 changes: 19 additions & 7 deletions lib/core_dom/element_binder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ class ElementBinder {
bool get shouldCompileChildren =>
childMode == Directive.COMPILE_CHILDREN;

List<String> _bindAssignablePropsOnCache;
List<String> get _bindAssignablePropsOn {
if (_bindAssignablePropsOnCache != null) return _bindAssignablePropsOnCache;
_bindAssignablePropsOnCache = [];
_usableDirectiveRefs.forEach((DirectiveRef ref) {
var eventNames = ref.annotation.updateBoundElementPropertiesOnEvents;
if (eventNames != null) {
_bindAssignablePropsOnCache.addAll(eventNames);
}
});
if (_bindAssignablePropsOnCache.isEmpty) _bindAssignablePropsOnCache.add('change');
return _bindAssignablePropsOnCache;
}

var _directiveCache;
List<DirectiveRef> get _usableDirectiveRefs {
if (_directiveCache != null) return _directiveCache;
Expand Down Expand Up @@ -308,13 +322,11 @@ class ElementBinder {
// due to https://code.google.com/p/dart/issues/detail?id=17406
// we have to manually run the zone.
var zone = Zone.current;
node.addEventListener('change', (_) =>
zone.run(() =>
bindAssignableProps.forEach((propAndExp) =>
propAndExp[1].assign(scope.context, jsNode[propAndExp[0]])
)
)
);
_bindAssignablePropsOn.forEach((String eventName) =>
node.addEventListener(eventName, (_) =>
zone.run(() =>
bindAssignableProps.forEach((propAndExp) =>
propAndExp[1].assign(scope.context, jsNode[propAndExp[0]])))));
}

if (onEvents.isNotEmpty) {
Expand Down
6 changes: 4 additions & 2 deletions test/core/annotation_src_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ void main() => describe('annotations', () {
visibility: Directive.LOCAL_VISIBILITY,
exportExpressions: [],
exportExpressionAttrs: [],
useShadowDom: true
useShadowDom: true,
updateBoundElementPropertiesOnEvents: []
);

// Check that no fields are null
Expand All @@ -64,7 +65,8 @@ void main() => describe('annotations', () {
module: (i){},
visibility: Directive.LOCAL_VISIBILITY,
exportExpressions: [],
exportExpressionAttrs: []
exportExpressionAttrs: [],
updateBoundElementPropertiesOnEvents: []
);

// Check that no fields are null
Expand Down
25 changes: 25 additions & 0 deletions test/core_dom/web_components_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ main() {
beforeEach((TestBed tb) {
_ = tb;
});

beforeEachModule((Module m) {
m.bind(TestsTwoWayCustom);
});

it('should create custom elements', () {
registerElement('tests-basic', {'prop-x': 6});
Expand Down Expand Up @@ -89,5 +93,26 @@ main() {

expect(_.rootScope.context['x']).toEqual(6);
});

it('should support two-way bindings for components that trigger a defined event', () {
registerElement('tests-twoway-custom', {});
compileAndUpgrade('<tests-twoway-custom bind-prop="x"></tests-twoway-custom>');

setCustomProp('prop', 6);
_.rootElement.dispatchEvent(new Event.eventType('CustomEvent', 'x-change'));

expect(_.rootScope.context['x']).toEqual(6);

// The change event should not cause an update
setCustomProp('prop', 7);
_.rootElement.dispatchEvent(new Event.eventType('CustomEvent', 'change'));
expect(_.rootScope.context['x']).toEqual(6);
});
});
}

@Decorator(
selector: 'tests-twoway-custom',
updateBoundElementPropertiesOnEvents: const ['x-change']
)
class TestsTwoWayCustom {}

0 comments on commit 94c3522

Please sign in to comment.