Repository: teleporthq/teleport-code-generators (ts)

Issue:
Handling multiple statements in event handlers
When adding more than one statement in an event handler:
![Screenshot 2019-08-22 at 16 40 12](https://user-images.githubusercontent.com/9945366/63519429-98befe80-c4fb-11e9-8df0-78c38838e064.png)

The template part is properly updated to call a function, but the function is not implemented/added on the component class

![Screenshot 2019-08-22 at 16 40 04](https://user-images.githubusercontent.com/9945366/63519466-b12f1900-c4fb-11e9-9472-0c8780a31619.png)

Also notice the propCall is not handled, it uses the same syntax as vue, I think this is the solution we're looking for: https://angular.io/api/core/EventEmitter

Pull request:
fix(angular-base): Handling multiple statements in event handlers
Fixes #350 

Patch:
diff --git a/examples/uidl-samples/project.json b/examples/uidl-samples/project.json
index 81b5fa5b..aff0a81b 100644
--- a/examples/uidl-samples/project.json
+++ b/examples/uidl-samples/project.json
@@ -37,9 +37,7 @@
             "value": "home",
             "meta": {
               "path": "/",
-              "componentName": "Home"
-            },
-            "seo": {
+              "componentName": "Home",
               "title": "Home Page"
             }
           },
@@ -47,9 +45,7 @@
             "value": "team",
             "meta": {
               "path": "/team",
-              "componentName": "Team"
-            },
-            "seo": {
+              "componentName": "Team",
               "title": "About Page",
               "metaTags": [
                 { "name": "description", "content": "What is this about?" },
@@ -57,9 +53,6 @@
                 { "name": "viewport", "content": "width=device-width, initial-scale=1.0" },
                 { "property": "og:title", "content": "What is this about?" },
                 { "property": "og:url", "content": "/playground_assets/asdasd.png" }
-              ],
-              "assets": [
-                { "type": "canonical", "path": "https://teleporthq.io" }
               ]
             }
           },
@@ -68,11 +61,6 @@
             "meta": {
               "path": "/contact-us",
               "componentName": "ContactPage"
-            },
-            "seo": {
-              "assets": [
-                { "type": "canonical", "path": "https://teleporthq.io" }
-              ]
             }
           }
         ]
diff --git a/packages/teleport-component-generator/src/resolver/utils.ts b/packages/teleport-component-generator/src/resolver/utils.ts
index 181af18f..b90130c8 100644
--- a/packages/teleport-component-generator/src/resolver/utils.ts
+++ b/packages/teleport-component-generator/src/resolver/utils.ts
@@ -73,11 +73,11 @@ export const resolveNavlinks = (uidlNode: UIDLNode, routesDefinition: UIDLStateD
 }
 
 export const resolveMetaTags = (uidl: ComponentUIDL, options: GeneratorOptions) => {
-  if (!uidl.seo || !uidl.seo.metaTags || !options.assetsPrefix) {
+  if (!uidl.meta || !uidl.meta.metaTags || !options.assetsPrefix) {
     return
   }
 
-  uidl.seo.metaTags.forEach((tag) => {
+  uidl.meta.metaTags.forEach((tag) => {
     Object.keys(tag).forEach((key) => {
       tag[key] = prefixPlaygroundAssetsURL(options.assetsPrefix, tag[key])
     })
diff --git a/packages/teleport-plugin-angular-base-component/src/index.ts b/packages/teleport-plugin-angular-base-component/src/index.ts
index d3677377..f681d513 100644
--- a/packages/teleport-plugin-angular-base-component/src/index.ts
+++ b/packages/teleport-plugin-angular-base-component/src/index.ts
@@ -58,6 +58,7 @@ export const createPlugin: ComponentPluginFactory<AngularPluginConfig> = (config
       {
         interpolation: (value) => `{{ ${value} }}`,
         eventBinding: (value) => `(${value})`,
+        eventHandlersBindingMode: (value) => `${value}()`,
         valueBinding: (value, node?: UIDLElementNode) =>
           node && node.content.dependency ? `[${value}]` : `[attr.${value}]`,
         eventEmmitter: (value) => `this.$emit('${value}')`,
@@ -96,7 +97,30 @@ export const createPlugin: ComponentPluginFactory<AngularPluginConfig> = (config
       content: componentDecoratorAST,
     })
 
-    const exportAST = generateExportAST(uidl.name, propDefinitions, stateDefinitions, dataObject)
+    /* We need to import EventEmitter and Output in Angular to temit events to the parent
+    So, to make sure if we need to import them we need to loop through all the methods and
+    check if any of them are referring to the function that is passed as prop*/
+    if (Object.keys(methodsObject).length > 0) {
+      const shouldImportEventEmitter = Object.keys(methodsObject).some((method) => {
+        const statements = methodsObject[method]
+        if (statements.length > 0) {
+          return statements.some((event) => event.type === 'propCall')
+        }
+        return false
+      })
+      if (shouldImportEventEmitter) {
+        dependencies.Output = ANGULAR_CORE_DEPENDENCY
+        dependencies.EventEmitter = ANGULAR_CORE_DEPENDENCY
+      }
+    }
+
+    const exportAST = generateExportAST(
+      uidl.name,
+      propDefinitions,
+      stateDefinitions,
+      dataObject,
+      methodsObject
+    )
 
     chunks.push({
       type: CHUNK_TYPE.AST,
diff --git a/packages/teleport-plugin-angular-base-component/src/utils.ts b/packages/teleport-plugin-angular-base-component/src/utils.ts
index 1758b344..673615e6 100644
--- a/packages/teleport-plugin-angular-base-component/src/utils.ts
+++ b/packages/teleport-plugin-angular-base-component/src/utils.ts
@@ -2,24 +2,50 @@ import * as types from '@babel/types'
 import {
   convertValueToLiteral,
   getTSAnnotationForType,
+  createStateChangeStatement,
 } from '@teleporthq/teleport-shared/dist/cjs/utils/ast-js-utils'
-import { UIDLPropDefinition, UIDLStateDefinition } from '@teleporthq/teleport-types'
+import {
+  UIDLPropDefinition,
+  UIDLStateDefinition,
+  UIDLEventHandlerStatement,
+} from '@teleporthq/teleport-types'
 
 export const generateExportAST = (
   componentName: string,
   propDefinitions: Record<string, UIDLPropDefinition>,
   stateDefinitions: Record<string, UIDLStateDefinition>,
   dataObject: Record<string, any>,
+  methodsObject: Record<string, UIDLEventHandlerStatement[]>,
   t = types
 ) => {
-  const propDeclaration = Object.keys(propDefinitions).map((propKey) =>
-    t.classProperty(
+  let angularMethodsAST = []
+  if (Object.keys(methodsObject).length > 0) {
+    angularMethodsAST = createMethodsObject(methodsObject, propDefinitions)
+  }
+
+  const propDeclaration = Object.keys(propDefinitions).map((propKey) => {
+    const definition = propDefinitions[propKey]
+    // By default any prop with type function is used to emitting events to the callee
+    if (definition.type === 'func') {
+      return t.classProperty(
+        t.identifier(propKey),
+        t.newExpression(t.identifier('EventEmitter'), []),
+        t.typeAnnotation(
+          t.genericTypeAnnotation(
+            t.identifier('EventEmitter'),
+            t.typeParameterInstantiation([t.anyTypeAnnotation()])
+          )
+        ),
+        [t.decorator(t.callExpression(t.identifier('Output'), []))]
+      )
+    }
+    return t.classProperty(
       t.identifier(propKey),
       convertValueToLiteral(propDefinitions[propKey].defaultValue),
       t.tsTypeAnnotation(getTSAnnotationForType(propDefinitions[propKey].type)),
       [t.decorator(t.callExpression(t.identifier('Input'), []))]
     )
-  )
+  })
 
   const propertyDecleration = Object.keys(stateDefinitions).map((stateKey) =>
     t.classProperty(
@@ -43,6 +69,7 @@ export const generateExportAST = (
       ...propertyDecleration,
       ...dataDeclaration,
       constructorAST(),
+      ...angularMethodsAST,
     ])
   }
 
@@ -56,3 +83,54 @@ export const generateExportAST = (
 const constructorAST = (t = types) => {
   return t.classMethod('constructor', t.identifier('constructor'), [], t.blockStatement([]))
 }
+
+const createMethodsObject = (
+  methods: Record<string, UIDLEventHandlerStatement[]>,
+  propDefinitions: Record<string, UIDLPropDefinition>,
+  t = types
+) => {
+  return Object.keys(methods).map((eventKey) => {
+    const astStatements = []
+    methods[eventKey].map((statement) => {
+      const astStatement =
+        statement.type === 'propCall'
+          ? createPropCallStatement(statement, propDefinitions)
+          : createStateChangeStatement(statement)
+
+      if (astStatement) {
+        astStatements.push(astStatement)
+      }
+    })
+    return t.classMethod('method', t.identifier(eventKey), [], t.blockStatement(astStatements))
+  })
+}
+
+export const createPropCallStatement = (
+  eventHandlerStatement: UIDLEventHandlerStatement,
+  propDefinitions: Record<string, UIDLPropDefinition>,
+  t = types
+) => {
+  const { calls: propFunctionKey } = eventHandlerStatement
+
+  if (!propFunctionKey) {
+    console.warn(`No prop definition referenced under the "calls" field`)
+    return null
+  }
+
+  const propDefinition = propDefinitions[propFunctionKey]
+
+  if (!propDefinition) {
+    console.warn(`No prop definition was found for function "${propFunctionKey}"`)
+    return null
+  }
+
+  return t.expressionStatement(
+    t.callExpression(
+      t.memberExpression(
+        t.memberExpression(t.thisExpression(), t.identifier(propFunctionKey)),
+        t.identifier('emit')
+      ),
+      []
+    )
+  )
+}
diff --git a/packages/teleport-plugin-jsx-head-config/src/index.ts b/packages/teleport-plugin-jsx-head-config/src/index.ts
index 836d9398..b16fd8d5 100644
--- a/packages/teleport-plugin-jsx-head-config/src/index.ts
+++ b/packages/teleport-plugin-jsx-head-config/src/index.ts
@@ -30,20 +30,16 @@ export const createPlugin: ComponentPluginFactory<JSXHeadPluginConfig> = (config
       )
     }
 
-    if (!uidl.seo) {
-      return structure
-    }
-
     const headASTTags = []
 
-    if (uidl.seo.title) {
+    if (uidl.meta && uidl.meta.title) {
       const titleAST = createJSXTag('title')
-      addChildJSXText(titleAST, uidl.seo.title)
+      addChildJSXText(titleAST, uidl.meta.title)
       headASTTags.push(titleAST)
     }
 
-    if (uidl.seo.metaTags) {
-      uidl.seo.metaTags.forEach((tag) => {
+    if (uidl.meta && uidl.meta.metaTags) {
+      uidl.meta.metaTags.forEach((tag) => {
         const metaAST = createSelfClosingJSXTag('meta')
         Object.keys(tag).forEach((key) => {
           addAttributeToJSXTag(metaAST, key, tag[key])
@@ -52,18 +48,6 @@ export const createPlugin: ComponentPluginFactory<JSXHeadPluginConfig> = (config
       })
     }
 
-    if (uidl.seo.assets) {
-      uidl.seo.assets.forEach((asset) => {
-        // TODO: Handle other asset types when needed
-        if (asset.type === 'canonical') {
-          const canonicalLink = createSelfClosingJSXTag('link')
-          addAttributeToJSXTag(canonicalLink, 'rel', 'canonical')
-          addAttributeToJSXTag(canonicalLink, 'href', asset.path)
-          headASTTags.push(canonicalLink)
-        }
-      })
-    }
-
     if (headASTTags.length > 0) {
       const headConfigTag = createJSXTag(configTagIdentifier, headASTTags)
 
diff --git a/packages/teleport-plugin-vue-base-component/src/utils.ts b/packages/teleport-plugin-vue-base-component/src/utils.ts
index bcac9bd3..babdc736 100644
--- a/packages/teleport-plugin-vue-base-component/src/utils.ts
+++ b/packages/teleport-plugin-vue-base-component/src/utils.ts
@@ -1,9 +1,10 @@
 import * as types from '@babel/types'
 
 import {
-  objectToObjectExpression,
-  convertValueToLiteral,
   ParsedASTNode,
+  convertValueToLiteral,
+  objectToObjectExpression,
+  createStateChangeStatement,
 } from '@teleporthq/teleport-shared/dist/cjs/utils/ast-js-utils'
 import {
   UIDLPropDefinition,
@@ -147,24 +148,7 @@ const createMethodsObject = (
   })
 }
 
-const createStateChangeStatement = (statement: UIDLEventHandlerStatement, t = types) => {
-  const { modifies, newState } = statement
-
-  const rightOperand =
-    newState === '$toggle'
-      ? t.unaryExpression('!', t.memberExpression(t.identifier('this'), t.identifier(modifies)))
-      : convertValueToLiteral(newState)
-
-  return t.expressionStatement(
-    t.assignmentExpression(
-      '=',
-      t.memberExpression(t.identifier('this'), t.identifier(modifies)),
-      rightOperand
-    )
-  )
-}
-
-const createPropCallStatement = (
+export const createPropCallStatement = (
   eventHandlerStatement: UIDLEventHandlerStatement,
   propDefinitions: Record<string, UIDLPropDefinition>,
   t = types
diff --git a/packages/teleport-plugin-vue-head-config/src/index.ts b/packages/teleport-plugin-vue-head-config/src/index.ts
index b70d9e45..2009574e 100644
--- a/packages/teleport-plugin-vue-head-config/src/index.ts
+++ b/packages/teleport-plugin-vue-head-config/src/index.ts
@@ -21,24 +21,14 @@ export const createPlugin: ComponentPluginFactory<VueMetaPluginConfig> = (config
     const headObject: {
       title?: string
       meta?: Array<Record<string, string>>
-      link?: Array<Record<string, string>>
     } = {}
 
-    if (uidl.seo && uidl.seo.title) {
-      headObject.title = uidl.seo.title
+    if (uidl.meta && uidl.meta.title) {
+      headObject.title = uidl.meta.title
     }
 
-    if (uidl.seo && uidl.seo.metaTags) {
-      headObject.meta = uidl.seo.metaTags
-    }
-
-    if (uidl.seo.assets) {
-      uidl.seo.assets.forEach((asset) => {
-        // TODO: Handle other asset types when needed
-        if (asset.type === 'canonical') {
-          headObject.link = [{ rel: 'canonical', href: asset.path }]
-        }
-      })
+    if (uidl.meta && uidl.meta.metaTags) {
+      headObject.meta = uidl.meta.metaTags
     }
 
     if (Object.keys(headObject).length > 0) {
diff --git a/packages/teleport-project-generator-next/src/utils.ts b/packages/teleport-project-generator-next/src/utils.ts
index 5e4d850a..f6993d67 100644
--- a/packages/teleport-project-generator-next/src/utils.ts
+++ b/packages/teleport-project-generator-next/src/utils.ts
@@ -1,6 +1,7 @@
 import {
   addAttributeToJSXTag,
   addChildJSXTag,
+  addChildJSXText,
 } from '@teleporthq/teleport-shared/dist/cjs/utils/ast-jsx-utils'
 import { createJSXTag } from '@teleporthq/teleport-shared/dist/cjs/builders/ast-builders'
 
@@ -29,7 +30,11 @@ export const createDocumentFileChunks = (uidl: ProjectUIDL, options: EntryFileOp
     addAttributeToJSXTag(htmlNode, 'lang', settings.language)
   }
 
-  // NOTE: Title is added in per page, not in the layout file
+  if (settings.title) {
+    const titleTag = createJSXTag('title')
+    addChildJSXText(titleTag, settings.title)
+    addChildJSXTag(headNode, titleTag)
+  }
 
   if (manifest) {
     const linkTag = createJSXTag('link')
@@ -50,14 +55,6 @@ export const createDocumentFileChunks = (uidl: ProjectUIDL, options: EntryFileOp
   assets.forEach((asset) => {
     const assetPath = prefixPlaygroundAssetsURL(options.assetsPrefix, asset.path)
 
-    // link canonical for SEO
-    if (asset.type === 'canonical' && assetPath) {
-      const linkTag = createJSXTag('link')
-      addAttributeToJSXTag(linkTag, 'rel', 'canonical')
-      addAttributeToJSXTag(linkTag, 'href', assetPath)
-      addChildJSXTag(headNode, linkTag)
-    }
-
     // link stylesheet (external css, font)
     if ((asset.type === 'style' || asset.type === 'font') && assetPath) {
       const linkTag = createJSXTag('link')
diff --git a/packages/teleport-project-generator/src/file-handlers.ts b/packages/teleport-project-generator/src/file-handlers.ts
index 234c9a7f..efda8c00 100644
--- a/packages/teleport-project-generator/src/file-handlers.ts
+++ b/packages/teleport-project-generator/src/file-handlers.ts
@@ -192,14 +192,6 @@ const createHTMLEntryFileChunks = (uidl: ProjectUIDL, options: EntryFileOptions)
   assets.forEach((asset) => {
     const assetPath = prefixPlaygroundAssetsURL(assetsPrefix, asset.path)
 
-    // link canonical for SEO
-    if (asset.type === 'canonical' && assetPath) {
-      const linkTag = createHTMLNode('link')
-      addAttributeToNode(linkTag, 'rel', 'canonical')
-      addAttributeToNode(linkTag, 'href', assetPath)
-      addChildNode(headNode, linkTag)
-    }
-
     // link stylesheet (external css, font)
     if ((asset.type === 'style' || asset.type === 'font') && assetPath) {
       const linkTag = createHTMLNode('link') // , { selfClosing: true })
diff --git a/packages/teleport-project-generator/src/index.ts b/packages/teleport-project-generator/src/index.ts
index 8126a792..cc7a9242 100644
--- a/packages/teleport-project-generator/src/index.ts
+++ b/packages/teleport-project-generator/src/index.ts
@@ -61,7 +61,7 @@ export const createProjectGenerator = (strategy: ProjectStrategy): ProjectGenera
     const { components = {}, root } = uidl
 
     // Based on the routing roles, separate pages into distict UIDLs with their own file names and paths
-    const pageUIDLs = createPageUIDLs(uidl, strategy)
+    const pageUIDLs = createPageUIDLs(root, strategy)
 
     // Set the filename and path for each component based on the strategy
     prepareComponentFilenamesAndPath(components, strategy)
diff --git a/packages/teleport-project-generator/src/utils.ts b/packages/teleport-project-generator/src/utils.ts
index 7c55e1a8..fdbe41e5 100644
--- a/packages/teleport-project-generator/src/utils.ts
+++ b/packages/teleport-project-generator/src/utils.ts
@@ -11,29 +11,33 @@ import {
   GeneratedFolder,
   UIDLElement,
   ComponentUIDL,
-  ProjectUIDL,
   UIDLConditionalNode,
+  UIDLStateDefinition,
   ProjectStrategy,
 } from '@teleporthq/teleport-types'
 import { elementNode } from '@teleporthq/teleport-shared/dist/cjs/builders/uidl-builders'
 
-export const createPageUIDLs = (uidl: ProjectUIDL, strategy: ProjectStrategy): ComponentUIDL[] => {
-  const routeNodes = extractRoutes(uidl.root)
-  return routeNodes.map((routeNode) => createPageUIDL(routeNode, uidl, strategy))
+export const createPageUIDLs = (
+  root: ComponentUIDL,
+  strategy: ProjectStrategy
+): ComponentUIDL[] => {
+  const routeNodes = extractRoutes(root)
+  return routeNodes.map((routeNode) =>
+    createPageUIDL(routeNode, root.stateDefinitions.route, strategy)
+  )
 }
 
 const createPageUIDL = (
   routeNode: UIDLConditionalNode,
-  uidl: ProjectUIDL,
+  routeDefintion: UIDLStateDefinition,
   strategy: ProjectStrategy
 ): ComponentUIDL => {
   const { value, node } = routeNode.content
   const pageName = value.toString()
-  const routeDefinition = uidl.root.stateDefinitions.route
   const pagesStrategyOptions = strategy.pages.options || {}
 
   const { componentName, fileName } = extractPageMetadata(
-    routeDefinition,
+    routeDefintion,
     pageName,
     strategy.pages.options
   )
@@ -43,7 +47,7 @@ const createPageUIDL = (
   const createPathInOwnFile =
     !pagesStrategyOptions.usePathAsFileName && pagesStrategyOptions.createFolderForEachComponent
 
-  const meta = createPathInOwnFile
+  const fileMeta = createPathInOwnFile
     ? {
         fileName: pagesStrategyOptions.customComponentFileName || 'index',
         styleFileName: pagesStrategyOptions.customStyleFileName || 'style',
@@ -58,13 +62,10 @@ const createPageUIDL = (
       }
 
   // Looking into the state definition, we take the seo information for the corresponding page
-  // If no title is provided for the page, the global settings title is passed as a default
-  const pageDefinition = routeDefinition.values.find((stateDef) => stateDef.value === pageName)
-  const title = (pageDefinition.seo && pageDefinition.seo.title) || uidl.globals.settings.title
-  const seo = {
-    ...pageDefinition.seo,
-    title,
-  }
+  const pageDefinition = routeDefintion.values.find((stateDef) => stateDef.value === pageName)
+  const seoMeta = pageDefinition.meta
+    ? { title: pageDefinition.meta.title, metaTags: pageDefinition.meta.metaTags }
+    : null
 
   // Because conditional nodes accept any type of UIDLNode as a child
   // we need to ensure that the page is always of type 'element'
@@ -74,8 +75,7 @@ const createPageUIDL = (
   return {
     name: componentName,
     node: pageContent,
-    meta,
-    seo,
+    meta: { ...fileMeta, ...seoMeta },
   }
 }
 
diff --git a/packages/teleport-shared/src/builders/uidl-builders.ts b/packages/teleport-shared/src/builders/uidl-builders.ts
index 57efe32b..318705c9 100644
--- a/packages/teleport-shared/src/builders/uidl-builders.ts
+++ b/packages/teleport-shared/src/builders/uidl-builders.ts
@@ -13,6 +13,7 @@ import {
   UIDLStateDefinition,
   UIDLConditionalNode,
   UIDLDynamicReference,
+  UIDLEventDefinitions,
 } from '@teleporthq/teleport-types'
 
 export const component = (
@@ -44,13 +45,14 @@ export const elementNode = (
   attrs?: Record<string, UIDLAttributeValue>,
   children?: UIDLNode[],
   dependency?: UIDLDependency,
-  style?: Record<string, UIDLStyleValue>
+  style?: Record<string, UIDLStyleValue>,
+  events?: UIDLEventDefinitions
 ): UIDLElementNode => {
   return {
     type: 'element',
     content: style
-      ? element(elementType, attrs, children, dependency, style)
-      : element(elementType, attrs, children, dependency),
+      ? element(elementType, attrs, children, dependency, events, style)
+      : element(elementType, attrs, children, dependency, events),
   }
 }
 
@@ -59,6 +61,7 @@ export const element = (
   attrs?: Record<string, UIDLAttributeValue>,
   children?: UIDLNode[],
   dependency?: UIDLDependency,
+  events?: UIDLEventDefinitions,
   style?: Record<string, UIDLStyleValue>
 ) => {
   if (dependency) {
@@ -68,6 +71,7 @@ export const element = (
       dependency,
       attrs,
       style,
+      events,
       children,
     }
   }
@@ -76,6 +80,7 @@ export const element = (
     name: elementType,
     attrs,
     style,
+    events,
     children,
   }
 }
diff --git a/packages/teleport-shared/src/node-handlers/node-to-html/constants.ts b/packages/teleport-shared/src/node-handlers/node-to-html/constants.ts
index 4b0c1ea1..7769cc1f 100644
--- a/packages/teleport-shared/src/node-handlers/node-to-html/constants.ts
+++ b/packages/teleport-shared/src/node-handlers/node-to-html/constants.ts
@@ -6,6 +6,7 @@ export const DEFAULT_TEMPLATE_SYNTAX: HTMLTemplateSyntax = {
   eventBinding: (value) => `@${value}`,
   valueBinding: (value) => `:${value}`,
   eventEmmitter: (value) => `this.$emit('${value}')`,
+  eventHandlersBindingMode: (value) => value,
   conditionalAttr: 'v-if',
   repeatAttr: 'v-for',
   repeatIterator: (iteratorName, iteratedCollection, useIndex) => {
diff --git a/packages/teleport-shared/src/node-handlers/node-to-html/types.ts b/packages/teleport-shared/src/node-handlers/node-to-html/types.ts
index cb5a274d..1105652a 100644
--- a/packages/teleport-shared/src/node-handlers/node-to-html/types.ts
+++ b/packages/teleport-shared/src/node-handlers/node-to-html/types.ts
@@ -15,6 +15,7 @@ export interface HTMLTemplateSyntax {
   interpolation?: (value: string) => string
   valueBinding?: (value: string, node?: UIDLElementNode) => string
   eventBinding?: (value: string) => string
+  eventHandlersBindingMode?: (value: string) => string
   eventEmmitter?: (value: string) => string
   conditionalAttr?: string
   repeatAttr?: string
diff --git a/packages/teleport-shared/src/node-handlers/node-to-html/utils.ts b/packages/teleport-shared/src/node-handlers/node-to-html/utils.ts
index ac5479da..2fc08c9d 100644
--- a/packages/teleport-shared/src/node-handlers/node-to-html/utils.ts
+++ b/packages/teleport-shared/src/node-handlers/node-to-html/utils.ts
@@ -81,8 +81,12 @@ export const handleEvent = (
     const methodName = `handle${dashCaseToUpperCamelCase(elementName)}${dashCaseToUpperCamelCase(
       eventKey
     )}`
+    const eventNameBiding = templateSyntax.eventHandlersBindingMode
+      ? templateSyntax.eventHandlersBindingMode(methodName)
+      : methodName
+
     methodsObject[methodName] = eventHandlerStatements
-    htmlUtils.addAttributeToNode(htmlNode, eventHandlerKey, methodName)
+    htmlUtils.addAttributeToNode(htmlNode, eventHandlerKey, eventNameBiding)
   }
 }
 
diff --git a/packages/teleport-shared/src/utils/ast-js-utils.ts b/packages/teleport-shared/src/utils/ast-js-utils.ts
index 22d7d4dd..6885fc72 100644
--- a/packages/teleport-shared/src/utils/ast-js-utils.ts
+++ b/packages/teleport-shared/src/utils/ast-js-utils.ts
@@ -1,4 +1,5 @@
 import * as types from '@babel/types'
+import { UIDLEventHandlerStatement } from '@teleporthq/teleport-types'
 /**
  * A tricky way to pass down custom configuration into
  * the objectToObjectExpression values, to allow for member expressions like
@@ -111,3 +112,20 @@ export const getTSAnnotationForType = (type: any, t = types) => {
       return t.tsUnknownKeyword()
   }
 }
+
+export const createStateChangeStatement = (statement: UIDLEventHandlerStatement, t = types) => {
+  const { modifies, newState } = statement
+
+  const rightOperand =
+    newState === '$toggle'
+      ? t.unaryExpression('!', t.memberExpression(t.identifier('this'), t.identifier(modifies)))
+      : convertValueToLiteral(newState)
+
+  return t.expressionStatement(
+    t.assignmentExpression(
+      '=',
+      t.memberExpression(t.identifier('this'), t.identifier(modifies)),
+      rightOperand
+    )
+  )
+}
diff --git a/packages/teleport-types/src/uidl.ts b/packages/teleport-types/src/uidl.ts
index 53f1f55c..f59cd02b 100755
--- a/packages/teleport-types/src/uidl.ts
+++ b/packages/teleport-types/src/uidl.ts
@@ -16,7 +16,7 @@ export interface ProjectUIDL {
 }
 
 export interface GlobalAsset {
-  type: 'script' | 'style' | 'font' | 'canonical' | 'icon'
+  type: string
   path?: string
   content?: string
   options?: {
@@ -37,18 +37,13 @@ export interface ComponentUIDL {
     styleFileName?: string
     templateFileName?: string
     path?: string[]
+    title?: string
+    metaTags?: Array<Record<string, string>>
   }
-  seo?: UIDLComponentSEO
   propDefinitions?: Record<string, UIDLPropDefinition>
   stateDefinitions?: Record<string, UIDLStateDefinition>
 }
 
-export interface UIDLComponentSEO {
-  title?: string
-  metaTags?: Array<Record<string, string>>
-  assets?: GlobalAsset[]
-}
-
 export interface UIDLPropDefinition {
   type: string
   defaultValue?: string | number | boolean | any[] | object | (() => void)
@@ -65,8 +60,9 @@ export interface UIDLStateDefinition {
       componentName?: string
       path?: string
       fileName?: string
+      title?: string
+      metaTags?: Array<Record<string, string>>
     }
-    seo?: UIDLComponentSEO
     transitions?: any
   }>
   actions?: string[]


Test patch:
diff --git a/packages/teleport-component-generator-angular/__tests__/end2end/index.ts b/packages/teleport-component-generator-angular/__tests__/end2end/index.ts
index d74920b3..2fd054ef 100644
--- a/packages/teleport-component-generator-angular/__tests__/end2end/index.ts
+++ b/packages/teleport-component-generator-angular/__tests__/end2end/index.ts
@@ -4,11 +4,23 @@ import uidlSampleJSON from '../../../../examples/test-samples/component-sample.j
 import invalidUidlSampleJSON from '../../../../examples/test-samples/component-invalid-sample.json'
 
 import { createAngularComponentGenerator } from '../../src'
-import { ComponentUIDL, GeneratedFile } from '@teleporthq/teleport-types'
+import {
+  ComponentUIDL,
+  GeneratedFile,
+  UIDLEventDefinitions,
+  UIDLPropDefinition,
+} from '@teleporthq/teleport-types'
+import {
+  component,
+  elementNode,
+  dynamicNode,
+  staticNode,
+} from '@teleporthq/teleport-shared/dist/cjs/builders/uidl-builders'
 
 const uidlSample = uidlSampleJSON as ComponentUIDL
 const invalidUidlSample = invalidUidlSampleJSON as ComponentUIDL
 const TS_FILE = 'ts'
+const HTML_FILE = 'html'
 const findFileByType = (files: GeneratedFile[], type: string = TS_FILE) =>
   files.find((file) => file.fileType === type)
 
@@ -59,3 +71,51 @@ describe('Angular Component Validator', () => {
     expect(result.dependencies).toBeDefined()
   })
 })
+
+describe('Should add EventEmitter and Emit events when a fun is sent via prop', () => {
+  const generator = createAngularComponentGenerator()
+  const propDefinitions: Record<string, UIDLPropDefinition> = {
+    message: {
+      type: 'string',
+      defaultValue: 'Hello',
+    },
+    onClose: {
+      type: 'func',
+      defaultValue: '() => {}',
+    },
+  }
+  const events: UIDLEventDefinitions = {
+    click: [
+      {
+        type: 'propCall',
+        calls: 'onClose',
+      },
+      {
+        type: 'stateChange',
+        modifies: 'fakeState',
+        newState: false,
+      },
+    ],
+  }
+  const uidl: ComponentUIDL = component(
+    'PropEventComponent',
+    elementNode('container', {}, [
+      dynamicNode('prop', 'message'),
+      elementNode('button', {}, [staticNode('close')], null, null, events),
+    ]),
+    propDefinitions
+  )
+
+  it('Adds EmitEmitter to the import', async () => {
+    const result = await generator.generateComponent(uidl)
+    const tsFile = findFileByType(result.files, TS_FILE)
+    const htmlFile = findFileByType(result.files, HTML_FILE)
+
+    expect(result.files.length).toBe(2)
+    expect(tsFile.content).toContain(`Output, EventEmitter`)
+    expect(tsFile.content).toContain(`@Output`)
+    expect(tsFile.content).toContain(`onClose: EventEmitter<any> = new EventEmitter()`)
+    expect(tsFile.content).toContain(`this.onClose.emit()`)
+    expect(htmlFile.content).toContain(`(click)="handleButtonClick()"`)
+  })
+})
diff --git a/packages/teleport-component-generator-vue/__tests__/end2end/index.ts b/packages/teleport-component-generator-vue/__tests__/end2end/index.ts
index 65531792..61494c54 100644
--- a/packages/teleport-component-generator-vue/__tests__/end2end/index.ts
+++ b/packages/teleport-component-generator-vue/__tests__/end2end/index.ts
@@ -4,7 +4,18 @@ import uidlSampleJSON from '../../../../examples/test-samples/component-sample.j
 import invalidUidlSampleJSON from '../../../../examples/test-samples/component-invalid-sample.json'
 
 import { createVueComponentGenerator } from '../../src'
-import { ComponentUIDL, GeneratedFile } from '@teleporthq/teleport-types'
+import {
+  ComponentUIDL,
+  GeneratedFile,
+  UIDLPropDefinition,
+  UIDLEventDefinitions,
+} from '@teleporthq/teleport-types'
+import {
+  component,
+  elementNode,
+  dynamicNode,
+  staticNode,
+} from '@teleporthq/teleport-shared/dist/cjs/builders/uidl-builders'
 
 const uidlSample = uidlSampleJSON as ComponentUIDL
 const invalidUidlSample = invalidUidlSampleJSON as ComponentUIDL
@@ -68,3 +79,46 @@ describe('Vue Component Validator', () => {
     expect(result.dependencies).toBeDefined()
   })
 })
+
+describe('Should add EventEmitter and Emit events when a fun is sent via prop', () => {
+  const generator = createVueComponentGenerator()
+  const propDefinitions: Record<string, UIDLPropDefinition> = {
+    message: {
+      type: 'string',
+      defaultValue: 'Hello',
+    },
+    onClose: {
+      type: 'func',
+      defaultValue: '() => {}',
+    },
+  }
+  const events: UIDLEventDefinitions = {
+    click: [
+      {
+        type: 'propCall',
+        calls: 'onClose',
+      },
+      {
+        type: 'stateChange',
+        modifies: 'fakeState',
+        newState: false,
+      },
+    ],
+  }
+  const uidl: ComponentUIDL = component(
+    'PropEventComponent',
+    elementNode('container', {}, [
+      dynamicNode('prop', 'message'),
+      elementNode('button', {}, [staticNode('close')], null, null, events),
+    ]),
+    propDefinitions
+  )
+
+  it('Adds EmitEmitter to the import', async () => {
+    const result = await generator.generateComponent(uidl)
+    const vueFile = findFileByType(result.files, VUE_FILE)
+
+    expect(vueFile.content).toContain(`@click="handleButtonClick"`)
+    expect(vueFile.content).toContain(`this.$emit('onClose')`)
+  })
+})
diff --git a/packages/teleport-plugin-jsx-head-config/__tests__/index.ts b/packages/teleport-plugin-jsx-head-config/__tests__/index.ts
index 22d687fe..339e4e57 100644
--- a/packages/teleport-plugin-jsx-head-config/__tests__/index.ts
+++ b/packages/teleport-plugin-jsx-head-config/__tests__/index.ts
@@ -50,7 +50,7 @@ describe('plugin-jsx-head-config', () => {
   it('Should set the title in the <Helmet> component', async () => {
     const uidlSample = component('SimpleComponent', elementNode('container'))
     uidlSample.node.content.key = 'container'
-    uidlSample.seo = {
+    uidlSample.meta = {
       title: 'Test Title',
     }
 
@@ -78,7 +78,7 @@ describe('plugin-jsx-head-config', () => {
   it('Should set the meta tags in the <Helmet> component', async () => {
     const uidlSample = component('SimpleComponent', elementNode('container'))
     uidlSample.node.content.key = 'container'
-    uidlSample.seo = {
+    uidlSample.meta = {
       metaTags: [
         {
           name: 'description',
@@ -119,39 +119,4 @@ describe('plugin-jsx-head-config', () => {
     expect((randomKeyAttribute.name as types.JSXIdentifier).name).toBe('randomKey')
     expect((randomKeyAttribute.value as types.StringLiteral).value).toBe('randomValue')
   })
-
-  it('Should set the link tag in the <Helmet> for canonical', async () => {
-    const uidlSample = component('SimpleComponent', elementNode('container'))
-    uidlSample.node.content.key = 'container'
-    uidlSample.seo = {
-      assets: [
-        {
-          type: 'canonical',
-          path: 'https://teleporthq.io',
-        },
-      ],
-    }
-
-    const structure: ComponentStructure = {
-      uidl: uidlSample,
-      options: {},
-      chunks: [jsxChunk],
-      dependencies: {},
-    }
-
-    await plugin(structure)
-
-    const astNode = structure.chunks[0].meta.nodesLookup.container as types.JSXElement
-    const helmetNode = astNode.children[0] as types.JSXElement
-    expect((helmetNode.openingElement.name as types.JSXIdentifier).name).toBe('Helmet')
-
-    const linkNode = helmetNode.children[0] as types.JSXElement
-
-    const relAttribute = linkNode.openingElement.attributes[0] as types.JSXAttribute
-    const hrefAttribute = linkNode.openingElement.attributes[1] as types.JSXAttribute
-    expect((relAttribute.name as types.JSXIdentifier).name).toBe('rel')
-    expect((relAttribute.value as types.StringLiteral).value).toBe('canonical')
-    expect((hrefAttribute.name as types.JSXIdentifier).name).toBe('href')
-    expect((hrefAttribute.value as types.StringLiteral).value).toBe('https://teleporthq.io')
-  })
 })
diff --git a/packages/teleport-plugin-vue-head-config/__tests__/index.ts b/packages/teleport-plugin-vue-head-config/__tests__/index.ts
index 6bd75aec..70606658 100644
--- a/packages/teleport-plugin-vue-head-config/__tests__/index.ts
+++ b/packages/teleport-plugin-vue-head-config/__tests__/index.ts
@@ -25,7 +25,7 @@ describe('plugin-vue-head-config', () => {
   it('Should set the title in head object of the component', async () => {
     const uidlSample = component('SimpleComponent', elementNode('container'))
     uidlSample.node.content.key = 'container'
-    uidlSample.seo = {
+    uidlSample.meta = {
       title: 'Test Title',
     }
 
@@ -57,13 +57,13 @@ describe('plugin-vue-head-config', () => {
     const titleProperty = headObject.properties[0] as types.ObjectProperty
 
     expect(titleProperty.key.value).toBe('title')
-    expect((titleProperty.value as types.StringLiteral).value).toBe('Test Title')
+    expect(titleProperty.value.value).toBe('Test Title')
   })
 
-  it('Sets the meta tags in the component object', async () => {
+  it('Should set the meta tags in the <Helmet> component', async () => {
     const uidlSample = component('SimpleComponent', elementNode('container'))
     uidlSample.node.content.key = 'container'
-    uidlSample.seo = {
+    uidlSample.meta = {
       metaTags: [
         {
           name: 'description',
@@ -104,46 +104,4 @@ describe('plugin-vue-head-config', () => {
 
     expect(metaProperty.key.value).toBe('meta')
   })
-
-  it('Sets the link tag for the canonical in the component object', async () => {
-    const uidlSample = component('SimpleComponent', elementNode('container'))
-    uidlSample.node.content.key = 'container'
-    uidlSample.seo = {
-      assets: [
-        {
-          type: 'canonical',
-          path: 'https://teleporthq.io',
-        },
-      ],
-    }
-
-    const jsChunk = {
-      type: CHUNK_TYPE.AST,
-      fileType: FILE_TYPE.JS,
-      name: 'vue-js-chunk',
-      content: {
-        declaration: {
-          properties: [],
-        },
-      },
-      linkAfter: [],
-    }
-
-    const structure: ComponentStructure = {
-      uidl: uidlSample,
-      options: {},
-      chunks: [jsChunk],
-      dependencies: {},
-    }
-
-    await plugin(structure)
-
-    const headProperty = jsChunk.content.declaration.properties[0] as types.ObjectProperty
-    expect((headProperty.key as types.Identifier).name).toBe('head')
-
-    const headObject = headProperty.value as types.ObjectExpression
-    const linkProperty = headObject.properties[0] as types.ObjectProperty
-
-    expect(linkProperty.key.value).toBe('link')
-  })
 })