Skip to content

Commit

Permalink
feat(web components): Support custom events for element property binding
Browse files Browse the repository at this point in the history
  • Loading branch information
jbdeboer committed Sep 12, 2014
1 parent 5002583 commit 1a8fc52
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 13 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 @@ -315,8 +329,9 @@ class Component extends Directive {
exportExpressions,
exportExpressionAttrs,
this.useShadowDom,
this.useNgBaseCss: true})
: _cssUrls = cssUrl,
this.useNgBaseCss: true,
updateBoundElementPropertiesOnEvents
}) : _cssUrls = cssUrl,
_applyAuthorStyles = applyAuthorStyles,
_resetStyleInheritance = resetStyleInheritance,
super(selector: selector,
Expand All @@ -325,7 +340,8 @@ class Component extends Directive {
map: map,
module: module,
exportExpressions: exportExpressions,
exportExpressionAttrs: exportExpressionAttrs);
exportExpressionAttrs: exportExpressionAttrs,
updateBoundElementPropertiesOnEvents: updateBoundElementPropertiesOnEvents);

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

/**
Expand All @@ -369,14 +386,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 @@ -386,7 +405,8 @@ class Decorator extends Directive {
selector: selector,
visibility: visibility,
exportExpressions: exportExpressions,
exportExpressionAttrs: exportExpressionAttrs);
exportExpressionAttrs: exportExpressionAttrs,
updateBoundElementPropertiesOnEvents: updateBoundElementPropertiesOnEvents);
}

/**
Expand Down
22 changes: 19 additions & 3 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 @@ -305,9 +319,11 @@ class ElementBinder {
});

if (bindAssignableProps.isNotEmpty) {
node.addEventListener('change', (_) {
bindAssignableProps.forEach((propAndExp) {
propAndExp[1].assign(scope.context, jsNode[propAndExp[0]]);
_bindAssignablePropsOn.forEach((String eventName) {
node.addEventListener(eventName, (_) {
bindAssignableProps.forEach((propAndExp) {
propAndExp[1].assign(scope.context, jsNode[propAndExp[0]]);
});
});
});
}
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 @@ -46,7 +46,8 @@ void main() => describe('annotations', () {
visibility: Directive.LOCAL_VISIBILITY,
exportExpressions: [],
exportExpressionAttrs: [],
useShadowDom: true
useShadowDom: true,
updateBoundElementPropertiesOnEvents: []
);

// Check that no fields are null
Expand All @@ -66,7 +67,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 1a8fc52

Please sign in to comment.