Skip to content

Commit

Permalink
feat(layout): add support for @super keyword
Browse files Browse the repository at this point in the history
@super keyword inherits the parent content for a given section
  • Loading branch information
thetutlage committed Mar 20, 2017
1 parent 92a6722 commit 8f79a9b
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 42 deletions.
12 changes: 11 additions & 1 deletion src/Tags/LayoutTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,23 @@ class LayoutTag extends BaseTag {
*/
processedSections.push(sectionName)

/**
* Find whether or not to inherit the default
* section.
*/
let inheritParent = false
if (childs.length && ['@super', '@super()'].indexOf(childs[0].body.trim()) > -1) {
childs.shift()
inheritParent = true
}

const sectionOutput = childs.map((child) => compiler.parseAndReturnLine(child)).join(buffer.EOL)

/**
* Store the output of section inside a key on the
* context sections object.
*/
buffer.writeLine(`this.context.sections[${sectionName}] = \`${sectionOutput}\``)
buffer.writeLine(`this.context.sections[${sectionName}] = {inheritParent: ${inheritParent}, content: \`${sectionOutput}\`}`)

/**
* ++ the line no for the `endsection` tag
Expand Down
35 changes: 22 additions & 13 deletions src/Tags/SectionTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

const BaseTag = require('./BaseTag')
const os = require('os')

/**
* The official section tag. It is used
Expand Down Expand Up @@ -73,32 +74,36 @@ class SectionTag extends BaseTag {
const slotName = this._compileStatement(lexer, body, lineno).toStatement()

/**
* Write an if block checking where the actual content of
* the section exists or not.
* Show the actual section content when section content is missing
* or @super tag was used.
*/
buffer.writeLine(`if (this.context.hasSection(${slotName})) {`)
buffer.writeLine(`if (!this.context.hasSection(${slotName}) || this.context.inheritSection(${slotName})) {`)
buffer.indent()

/**
* Write the sections content when section does exists.
* Process all the childs within the section tag
*/
buffer.writeToOutput(`\${this.context.outPutSection(${slotName})}`)
childs.forEach((child) => compiler.parseLine(child))

/**
* Closing if and starting else
* Closing the if statement
*/
buffer.dedent()
buffer.writeLine('} else { ')
buffer.writeLine('}')

/**
* If section was overrided, show the overriden content
*/
buffer.writeLine(`if (this.context.hasSection(${slotName})) {`)
buffer.indent()

/**
* Process all the childs when section content does
* not exists.
* Write the sections content when section does exists.
*/
childs.forEach((child) => compiler.parseLine(child))
buffer.writeToOutput(`\${this.context.outPutSection(${slotName})}`)

/**
* Close else
* Closing if tag
*/
buffer.dedent()
buffer.writeLine('}')
Expand All @@ -112,11 +117,15 @@ class SectionTag extends BaseTag {
*/
run (context) {
context.macro('hasSection', function (name) {
return !!this.sections[name]
return this.sections[name] && typeof (this.sections[name].content) !== 'undefined'
})

context.macro('inheritSection', function (name) {
return this.sections[name] && this.sections[name].inheritParent
})

context.macro('outPutSection', function (name) {
return this.sections[name]
return this.sections[name].content
})
}
}
Expand Down
13 changes: 13 additions & 0 deletions test-helpers/views/layouts/user.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>

@section('content')
{{ fetchUsers() }}
@endsection

</body>
</html>
89 changes: 61 additions & 28 deletions test/unit/template.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,44 @@ test.group('Template Compiler', (group) => {
`)
})

})

test.group('Template Runner', () => {
test('render a template by loading it from file', (assert) => {
const loader = new Loader(path.join(__dirname, '../../test-helpers/views'))
const template = new Template(this.tags, {}, loader)
template.sourceView('welcome')
const output = template.render('welcome', { username: 'virk' })
assert.equal(output.trim(), 'virk')
})

test('render a template from string', (assert) => {
const loader = new Loader(path.join(__dirname, '../../test-helpers/views'))
const template = new Template(this.tags, {}, loader)
const output = template.renderString('{{ username }}', { username: 'virk' })
assert.equal(output.trim(), 'virk')
})

test('make use of presenter when rendering the view', (assert) => {
const loader = new Loader(
path.join(__dirname, '../../test-helpers/views'),
path.join(__dirname, '../../test-helpers/presenters')
)
const template = new Template(this.tags, {}, loader)
const output = template.presenter('User').renderString('{{ username }}', { username: 'virk' })
assert.equal(output.trim(), 'VIRK')
})

test('pass locals when rendering the view', (assert) => {
const loader = new Loader(
path.join(__dirname, '../../test-helpers/views'),
path.join(__dirname, '../../test-helpers/presenters')
)
const template = new Template(this.tags, {}, loader)
const output = template.share({username: 'virk'}).renderString('{{ username }}')
assert.equal(output.trim(), 'virk')
})

test('ignore everything not inside sections when a layout is defined', (assert) => {
const loader = new Loader(path.join(__dirname, '../../test-helpers/views'))
const statement = dedent`
Expand Down Expand Up @@ -211,41 +249,36 @@ test.group('Template Compiler', (group) => {
assert.equal($('p').length, 0)
assert.equal($('h2').text().trim(), 'Hello world')
})
})

test.group('Template Runner', () => {
test('render a template by loading it from file', (assert) => {
test('append layout section value with template section when super keyword is used', (assert) => {
const loader = new Loader(path.join(__dirname, '../../test-helpers/views'))
const template = new Template(this.tags, {}, loader)
template.sourceView('welcome')
const output = template.render('welcome', { username: 'virk' })
assert.equal(output.trim(), 'virk')
})
const statement = dedent`
@layout('layouts.master')
@section('content')
@super
<h2> Hello world </h2>
@endsection
`

test('render a template from string', (assert) => {
const loader = new Loader(path.join(__dirname, '../../test-helpers/views'))
this.tags.section.run(Context)
const template = new Template(this.tags, {}, loader)
const output = template.renderString('{{ username }}', { username: 'virk' })
assert.equal(output.trim(), 'virk')
const output = template.renderString(statement)
const $ = cheerio.load(output)
assert.equal($('h2').length, 1)
assert.equal($('p').length, 1)
assert.equal($('h2').text().trim(), 'Hello world')
})

test('make use of presenter when rendering the view', (assert) => {
const loader = new Loader(
path.join(__dirname, '../../test-helpers/views'),
path.join(__dirname, '../../test-helpers/presenters')
)
const template = new Template(this.tags, {}, loader)
const output = template.presenter('User').renderString('{{ username }}', { username: 'virk' })
assert.equal(output.trim(), 'VIRK')
})
test('do not execute default content when section is overridden', (assert) => {
const loader = new Loader(path.join(__dirname, '../../test-helpers/views'))
const statement = dedent`
@layout('layouts.user')
@section('content')
@endsection
`

test('pass locals when rendering the view', (assert) => {
const loader = new Loader(
path.join(__dirname, '../../test-helpers/views'),
path.join(__dirname, '../../test-helpers/presenters')
)
this.tags.section.run(Context)
const template = new Template(this.tags, {}, loader)
const output = template.share({username: 'virk'}).renderString('{{ username }}')
assert.equal(output.trim(), 'virk')
template.renderString(statement)
})
})

0 comments on commit 8f79a9b

Please sign in to comment.