Skip to content

Commit

Permalink
feat(component): implement component tag
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jul 5, 2018
1 parent 726142a commit 01e8e95
Show file tree
Hide file tree
Showing 29 changed files with 347 additions and 5 deletions.
2 changes: 2 additions & 0 deletions fixtures/components-named-slots/alert.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{{ $slots.heading }}
{{ $slots.main }}
15 changes: 15 additions & 0 deletions fixtures/components-named-slots/compiled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(function (template, ctx) {
let out = ''
out += template.renderWithState('components-named-slots/alert', {}, { 'heading': (function (template, ctx) {
let out = ''
out += ' This is title'
out += '\n'
return out
})(template, ctx), 'main': (function (template, ctx) {
let out = ''
out += ' This is then body'
out += '\n'
return out
})(template, ctx) })
return out
})(template, ctx)
6 changes: 6 additions & 0 deletions fixtures/components-named-slots/index.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@component('components-named-slots/alert')
@slot('heading')
This is title
@endslot
This is then body
@endcomponent
2 changes: 2 additions & 0 deletions fixtures/components-named-slots/index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
3 changes: 3 additions & 0 deletions fixtures/components-named-slots/index.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This is title

This is then body
2 changes: 2 additions & 0 deletions fixtures/components-props/alert.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{{ title }}
{{ $slots.main }}
10 changes: 10 additions & 0 deletions fixtures/components-props/compiled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(function (template, ctx) {
let out = ''
out += template.renderWithState('components-props/alert', { 'title': 'H1' }, { 'main': (function (template, ctx) {
let out = ''
out += 'Hello world'
out += '\n'
return out
})(template, ctx) })
return out
})(template, ctx)
3 changes: 3 additions & 0 deletions fixtures/components-props/index.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@component('components-props/alert', { 'title': 'H1' })
Hello world
@endcomponent
2 changes: 2 additions & 0 deletions fixtures/components-props/index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
2 changes: 2 additions & 0 deletions fixtures/components-props/index.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
H1
Hello world
1 change: 1 addition & 0 deletions fixtures/components-state/alert.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ $slots.main }}
11 changes: 11 additions & 0 deletions fixtures/components-state/compiled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(function (template, ctx) {
let out = ''
out += template.renderWithState('components-state/alert', {}, { 'main': (function (template, ctx) {
let out = ''
out += ' Hello '
out += `${ctx.escape(ctx.resolve('username'))}`
out += '\n'
return out
})(template, ctx) })
return out
})(template, ctx)
3 changes: 3 additions & 0 deletions fixtures/components-state/index.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@component('components-state/alert')
Hello {{ username }}
@endcomponent
3 changes: 3 additions & 0 deletions fixtures/components-state/index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"username": "virk"
}
1 change: 1 addition & 0 deletions fixtures/components-state/index.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello virk
1 change: 1 addition & 0 deletions fixtures/components/alert.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ $slots.main }}
10 changes: 10 additions & 0 deletions fixtures/components/compiled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(function (template, ctx) {
let out = ''
out += template.renderWithState('components/alert', {}, { 'main': (function (template, ctx) {
let out = ''
out += ' Hello world'
out += '\n'
return out
})(template, ctx) })
return out
})(template, ctx)
3 changes: 3 additions & 0 deletions fixtures/components/index.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@component('components/alert')
Hello world
@endcomponent
2 changes: 2 additions & 0 deletions fixtures/components/index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
1 change: 1 addition & 0 deletions fixtures/components/index.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello world
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"dependencies": {
"deep-extend": "^0.6.0",
"edge-parser": "^1.0.8",
"edge-parser": "^1.0.9",
"he": "^1.1.1",
"macroable": "^1.0.0",
"node-exceptions": "^3.0.0"
Expand Down
46 changes: 46 additions & 0 deletions src/Tags/Component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Parser } from 'edge-parser'
import { EdgeBuffer } from 'edge-parser/build/src/EdgeBuffer'
import { IBlockNode } from 'edge-lexer/build/src/Contracts'
import { parseSequenceExpression, ObjectifyString } from '../utils'

export class ComponentTag {
public static block = true
public static seekable = true
public static selfclosed = true

/**
* Compiles else block node to Javascript else statement
*/
public compile (parser: Parser, buffer: EdgeBuffer, token: IBlockNode) {
const parsed = parser.generateAst(token.properties.jsArg, token.lineno)
const expression = parser.parseStatement(parsed.body[0])
let [name, props] = parseSequenceExpression(expression, parser)

const slots = {}
token.children.forEach((child) => {
let name = `'main'`
if (child.type === 'block' && (child as IBlockNode).properties.name === 'slot') {
name = (child as IBlockNode).properties.jsArg
}

slots[name] = slots[name] || new EdgeBuffer()
parser.processToken(child, slots[name])
})

const obj = new ObjectifyString()
Object.keys(slots).forEach((slot) => {
obj.add(slot, slots[slot].flush())
})

buffer.writeLine(`template.renderWithState(${name}, ${props}, ${obj.flush()})`)
}
}
2 changes: 1 addition & 1 deletion src/Tags/Include.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class IncludeTag {
*/
public compile (parser: Parser, buffer: EdgeBuffer, token: IBlockNode) {
const parsed = parser.parseJsArg(token.properties.jsArg, token.lineno)
disAllowExpressions('if', parsed, this.bannedExpressions)
disAllowExpressions('include', parsed, this.bannedExpressions)

/**
* Include template. Since the partials can be a runtime value, we cannot inline
Expand Down
27 changes: 27 additions & 0 deletions src/Tags/Slot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Parser } from 'edge-parser'
import { EdgeBuffer } from 'edge-parser/build/src/EdgeBuffer'
import { IBlockNode } from 'edge-lexer/build/src/Contracts'

export class SlotTag {
public static block = true
public static seekable = true
public static selfclosed = false

/**
* Compiles else block node to Javascript else statement
*/
public compile (parser: Parser, buffer: EdgeBuffer, token: IBlockNode) {
token.children.forEach((child, index) => {
parser.processToken(child, buffer)
})
}
}
2 changes: 2 additions & 0 deletions src/Tags/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export { ElseTag as else } from './Else'
export { ElseIfTag as elseif } from './ElseIf'
export { IncludeTag as include } from './Include'
export { EachTag as each } from './Each'
export { ComponentTag as component } from './Component'
export { SlotTag as slot } from './Slot'
8 changes: 8 additions & 0 deletions src/Template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { Context } from '../Context'
import { IPresenter } from '../Contracts'
import { Compiler } from '../Compiler'
import { Presenter } from '../Presenter'

export class Template {
constructor (private compiler: Compiler, private sharedState: any) {
Expand All @@ -19,6 +20,13 @@ export class Template {
return new Function('template', 'ctx', this.compiler.compile(template, 'default', true))
}

public renderWithState (template: string, state: object, slots: object): string {
const compiledTemplate = this.compiler.compile(template)
const presenter = new Presenter(Object.assign(state, { $slots: slots }))
const ctx = new Context(presenter, this.sharedState)
return new Function('template', 'ctx', `return ${compiledTemplate}`)(this, ctx)
}

public render (template: string, presenter: IPresenter, diskName?: string): string {
const compiledTemplate = this.compiler.compile(template, diskName)
const ctx = new Context(presenter, this.sharedState)
Expand Down
74 changes: 74 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,89 @@
*/

import { UnAllowedExpressionException } from '../Exceptions'
import { Parser } from 'edge-parser'

export class ObjectifyString {
private obj: string = ''

/**
* Add key/value pair to the object
*/
public add (key: any, value: any) {
this.obj += this.obj.length ? `, ${key}: ${value}` : `${key}: ${value}`
}

/**
* Returns the object alike string back
*/
public flush (): string {
const obj = this.obj
this.obj = ''

return `{ ${obj} }`
}
}

/**
* Validates the expression type to be part of the allowed
* expressions only
*/
export function allowExpressions (tag: string, expression: any, expressions: string[]) {
if (expressions.indexOf(expression.type) === -1) {
throw UnAllowedExpressionException.invoke('if', expression.type, expression.loc.start.line)
}
}

/**
* Validates the expression type to not be part of the black
* listed expressions.
*/
export function disAllowExpressions (tag: string, expression: any, expressions: string[]) {
if (expressions.indexOf(expression.type) > -1) {
throw UnAllowedExpressionException.invoke('if', expression.type, expression.loc.start.line)
}
}

/**
* Parses the sequence expression to an array of with first value as a string and
* other value as a string representation of the object.
*
* The idea is to make the sequence expression consumable for callable expressions.
* Check the following examples carefully.
*
* This helper is heavily used by component tag.
*
* ('foo.bar', title = 'hello')
* // returns ['foo.bar', { title: 'hello' }]
*
* ('foo.bar', { title: 'hello' })
* // returns ['foo.bar', { title: 'hello' }]
*
* (user.alert, { title: 'hello' })
* // return [ctx.resolve('user').alert, { title: 'hello' }]
*/
export function parseSequenceExpression (expression: any, parser: Parser): [string, string] {
if (expression.type === 'SequenceExpression') {
const objectifyString = new ObjectifyString()
const name = parser.statementToString(expression.expressions.shift())

expression.expressions.forEach((arg) => {
if (arg.type === 'ObjectExpression') {
arg.properties.forEach((prop) => {
const key = parser.statementToString(prop.key)
const value = parser.statementToString(prop.value)
objectifyString.add(key, value)
})
}

if (arg.type === 'AssignmentExpression') {
objectifyString.add(arg.left.name, parser.statementToString(arg.right))
}
})

return [name, objectifyString.flush()]
}

const name = parser.statementToString(expression)
return [name, `{}`]
}
Loading

0 comments on commit 01e8e95

Please sign in to comment.