/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {EventHandlerVars, convertActionBinding} from '../compiler_util/expression_converter'; import {createInlineArray} from '../compiler_util/identifier_util'; import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler'; import {Identifiers, createIdentifier} from '../identifiers'; import * as o from '../output/output_ast'; import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast'; import {CompileElement} from './compile_element'; import {CompileMethod} from './compile_method'; import {getHandleEventMethodName} from './util'; export function bindOutputs( boundEvents: BoundEventAst[], directives: DirectiveAst[], compileElement: CompileElement, bindToRenderer: boolean): boolean { const usedEvents = collectEvents(boundEvents, directives); if (!usedEvents.size) { return false; } if (bindToRenderer) { subscribeToRenderEvents(usedEvents, compileElement); } subscribeToDirectiveEvents(usedEvents, directives, compileElement); generateHandleEventMethod(boundEvents, directives, compileElement); return true; } function collectEvents( boundEvents: BoundEventAst[], directives: DirectiveAst[]): Map { const usedEvents = new Map(); boundEvents.forEach((event) => { usedEvents.set(event.fullName, event); }); directives.forEach((dirAst) => { dirAst.hostEvents.forEach((event) => { usedEvents.set(event.fullName, event); }); }); return usedEvents; } function subscribeToRenderEvents( usedEvents: Map, compileElement: CompileElement) { const eventAndTargetExprs: o.Expression[] = []; usedEvents.forEach((event) => { if (!event.phase) { eventAndTargetExprs.push(o.literal(event.name), o.literal(event.target)); } }); if (eventAndTargetExprs.length) { const disposableVar = o.variable(`disposable_${compileElement.view.disposables.length}`); compileElement.view.disposables.push(disposableVar); compileElement.view.createMethod.addStmt( disposableVar .set(o.importExpr(createIdentifier(Identifiers.subscribeToRenderElement)).callFn([ o.THIS_EXPR, compileElement.renderNode, createInlineArray(eventAndTargetExprs), handleEventExpr(compileElement) ])) .toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private])); } } function subscribeToDirectiveEvents( usedEvents: Map, directives: DirectiveAst[], compileElement: CompileElement) { const usedEventNames = Array.from(usedEvents.keys()); directives.forEach((dirAst) => { const dirWrapper = compileElement.directiveWrapperInstance.get(dirAst.directive.type.reference); compileElement.view.createMethod.addStmts(DirectiveWrapperExpressions.subscribe( dirAst.directive, dirAst.hostProperties, usedEventNames, dirWrapper, o.THIS_EXPR, handleEventExpr(compileElement))); }); } function generateHandleEventMethod( boundEvents: BoundEventAst[], directives: DirectiveAst[], compileElement: CompileElement) { const hasComponentHostListener = directives.some((dirAst) => dirAst.hostEvents.some((event) => dirAst.directive.isComponent)); const markPathToRootStart = hasComponentHostListener ? compileElement.compViewExpr : o.THIS_EXPR; const handleEventStmts = new CompileMethod(compileElement.view); handleEventStmts.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst); handleEventStmts.push(markPathToRootStart.callMethod('markPathToRootAsCheckOnce', []).toStmt()); const eventNameVar = o.variable('eventName'); const resultVar = o.variable('result'); handleEventStmts.push(resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)); directives.forEach((dirAst, dirIdx) => { const dirWrapper = compileElement.directiveWrapperInstance.get(dirAst.directive.type.reference); if (dirAst.hostEvents.length > 0) { handleEventStmts.push( resultVar .set(DirectiveWrapperExpressions .handleEvent( dirAst.hostEvents, dirWrapper, eventNameVar, EventHandlerVars.event) .and(resultVar)) .toStmt()); } }); boundEvents.forEach((renderEvent, renderEventIdx) => { const evalResult = convertActionBinding( compileElement.view, compileElement.view, compileElement.view.componentContext, renderEvent.handler, `sub_${renderEventIdx}`); const trueStmts = evalResult.stmts; if (evalResult.preventDefault) { trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt()); } // TODO(tbosch): convert this into a `switch` once our OutputAst supports it. handleEventStmts.push( new o.IfStmt(eventNameVar.equals(o.literal(renderEvent.fullName)), trueStmts)); }); handleEventStmts.push(new o.ReturnStatement(resultVar)); compileElement.view.methods.push(new o.ClassMethod( getHandleEventMethodName(compileElement.nodeIndex), [ new o.FnParam(eventNameVar.name, o.STRING_TYPE), new o.FnParam(EventHandlerVars.event.name, o.DYNAMIC_TYPE) ], handleEventStmts.finish(), o.BOOL_TYPE)); } function handleEventExpr(compileElement: CompileElement) { const handleEventMethodName = getHandleEventMethodName(compileElement.nodeIndex); return o.THIS_EXPR.callMethod('eventHandler', [o.THIS_EXPR.prop(handleEventMethodName)]); } type EventSummary = { name: string, target: string, phase: string };