This repository has been archived by the owner on Feb 22, 2018. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(EventHandler) Add support for on-* style events
- Loading branch information
Showing
14 changed files
with
233 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
part of angular.core.dom; | ||
|
||
typedef void EventFunction(event); | ||
|
||
@NgInjectableService() | ||
class EventHandler { | ||
dom.Node rootNode; | ||
final Expando expando; | ||
final ExceptionHandler exceptionHandler; | ||
final Map<String, Function> listeners = <String, Function>{}; | ||
|
||
EventHandler(this.rootNode, this.expando, this.exceptionHandler); | ||
|
||
void register(String eventName) { | ||
listeners.putIfAbsent(eventName, () { | ||
dom.EventListener eventListener = this.eventListener; | ||
rootNode.on[eventName].listen(eventListener); | ||
return eventListener; | ||
}); | ||
} | ||
|
||
eventListener(dom.Event event) { | ||
dom.Node element = event.target; | ||
while (element != null && element != rootNode) { | ||
var expression; | ||
if (element is dom.Element) | ||
expression = (element as dom.Element).attributes[eventNameToAttrName(event.type)]; | ||
if (expression != null) { | ||
try { | ||
var scope = getScope(element); | ||
if (scope != null) scope.eval(expression); | ||
} catch (e, s) { | ||
exceptionHandler(e, s); | ||
} | ||
} | ||
element = element.parentNode; | ||
} | ||
} | ||
|
||
Scope getScope(dom.Node element) { | ||
// var topElement = (rootNode is dom.ShadowRoot) ? rootNode.parentNode : rootNode; | ||
while (element != rootNode.parentNode) { | ||
ElementProbe probe = expando[element]; | ||
if (probe != null) { | ||
return probe.scope; | ||
} | ||
element = element.parentNode; | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Converts event name into attribute. Event named 'someCustomEvent' needs to | ||
* be transformed into on-some-custom-event. | ||
*/ | ||
static String eventNameToAttrName(String eventName) { | ||
var part = eventName.replaceAllMapped(new RegExp("([A-Z])"), (Match match) { | ||
return '-${match.group(0).toLowerCase()}'; | ||
}); | ||
return 'on-${part}'; | ||
} | ||
|
||
/** | ||
* Converts attribute into event name. Attribute 'on-some-custom-event' | ||
* corresponds to event named 'someCustomEvent'. | ||
*/ | ||
static String attrNameToEventName(String attrName) { | ||
var part = attrName.replaceAll("on-", ""); | ||
part = part.replaceAllMapped(new RegExp(r'\-(\w)'), (Match match) { | ||
return match.group(0).toUpperCase(); | ||
}); | ||
return part.replaceAll("-", ""); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
library event_handler_spec; | ||
|
||
import '../_specs.dart'; | ||
|
||
@NgController(selector: '[foo]', publishAs: 'ctrl') | ||
class FooController { | ||
var description = "desc"; | ||
var invoked = false; | ||
} | ||
|
||
@NgComponent(selector: 'bar', | ||
template: ''' | ||
<div> | ||
<span on-abc="ctrl.invoked=true;"></span> | ||
<content></content> | ||
</div> | ||
''', | ||
publishAs: 'ctrl') | ||
class BarComponent { | ||
var invoked = false; | ||
BarComponent(RootScope scope) { | ||
scope.context['ctrl'] = this; | ||
} | ||
} | ||
|
||
main() { | ||
describe('EventHandler', () { | ||
beforeEachModule((Module module) { | ||
module..type(FooController); | ||
module..type(BarComponent); | ||
module..value(Element, document.body); | ||
}); | ||
|
||
// Not sure why this doesn't work. | ||
afterEach(() { | ||
document.body.children.clear(); | ||
}); | ||
|
||
it('shoud register and handle event', inject((TestBed _) { | ||
document.body.append(_.compile( | ||
'''<div foo> | ||
<div on-abc="ctrl.invoked=true;"></div> | ||
</div>''')); | ||
|
||
document.querySelector('[on-abc]').dispatchEvent(new Event('abc')); | ||
var fooScope = _.getScope(document.querySelector('[foo]')); | ||
expect(fooScope.context['ctrl'].invoked).toEqual(true); | ||
document.body.children.clear(); | ||
})); | ||
|
||
it('shoud register and handle event with long name', inject((TestBed _) { | ||
document.body.append(_.compile( | ||
'''<div foo> | ||
<div on-my-new-event="ctrl.invoked=true;"></div> | ||
</div>''')); | ||
|
||
document.querySelector('[on-my-new-event]').dispatchEvent(new Event('myNewEvent')); | ||
var fooScope = _.getScope(document.querySelector('[foo]')); | ||
expect(fooScope.context['ctrl'].invoked).toEqual(true); | ||
document.body.children.clear(); | ||
})); | ||
|
||
it('shoud have model updates applied correctly', inject((TestBed _) { | ||
document.body.append(_.compile( | ||
'''<div foo> | ||
<div on-abc='ctrl.description="new description";'>{{ctrl.description}}</div> | ||
</div>''')); | ||
var el = document.querySelector('[on-abc]'); | ||
el.dispatchEvent(new Event('abc')); | ||
_.rootScope.apply(); | ||
expect(el.text).toEqual("new description"); | ||
document.body.children.clear(); | ||
})); | ||
|
||
it('shoud register event when shadow dom is used', async((TestBed _) { | ||
document.body.append(_.compile('<bar></bar>')); | ||
|
||
microLeap(); | ||
|
||
var shadowRoot = _.rootElement.shadowRoot; | ||
var span = shadowRoot.querySelector('span'); | ||
span.dispatchEvent(new CustomEvent('abc')); | ||
var ctrl = _.rootScope.context['ctrl']; | ||
expect(ctrl.invoked).toEqual(true); | ||
document.body.children.clear(); | ||
})); | ||
|
||
it('shoud handle event within content only once', async(inject((TestBed _) { | ||
document.body.append(_.compile( | ||
'''<div foo> | ||
<bar> | ||
<div on-abc="ctrl.invoked=true;"></div> | ||
</bar> | ||
</div>''')); | ||
|
||
microLeap(); | ||
|
||
document.querySelector('[on-abc]').dispatchEvent(new Event('abc')); | ||
var shadowRoot = document.querySelector('bar').shadowRoot; | ||
var shadowRootScope = _.getScope(shadowRoot); | ||
expect(shadowRootScope.context['ctrl'].invoked).toEqual(false); | ||
|
||
var fooScope = _.getScope(document.querySelector('[foo]')); | ||
expect(fooScope.context['ctrl'].invoked).toEqual(true); | ||
document.body.children.clear(); | ||
}))); | ||
}); | ||
} |
Oops, something went wrong.