Skip to content

Commit

Permalink
[FEATURE] Passing of tag content from f:render to partial/section
Browse files Browse the repository at this point in the history
Allows a completely new approach to structuring Fluid template rendering, allowing partials and sections to be used as "wrappers" for an arbitrary piece of template code. Consider the following example:

```xml
<f:section name="MyWrap">
    <div>
        <!-- And LOTS more HTML, using variables if desired -->
        <!-- Tag content of f:render output: -->
        {contentVariable -> f:format.raw()}
    </div>
</f:section>

<f:render section="MyWrap" contentAs="contentVariable">
    This content will be wrapped. Any Fluid code can go here.
</f:render>
```

The result is that the tag content of `f:render` is assigned as the template variable `{contentVariable}` which can then be used in the template code that is in the section/partial. It's the same as passing a big chunk of Fluid to the `arguments` array as a variable there, and using it the same way - but it is a lot easier on the syntax.

The new structure approach is enabled because `f:render` can then be used recursively to wrap your template code in any number of wraps (imagine the `f:section` tags with content similar to above):

```xml
<f:render partial="OuterWrap" contentAs="contentVariable">
    <f:render partial="InnerWrap" contentAs="contentVariable">
        <f:render partial="ItemWrap" contentAs="contentVariable">
            <h2>My item, nicely wrapped</h2>
            <p>Nice.</p>
        </f:render>
   </f:render>
</f:render>
```

Which would render as follows:

1. Outermost wrap renders tag content and assigns as variable for second level
2. Second level wrap renders tag content and assigns as variable for innermost level
3. Innermost level renders the actual content you wish to wrap and assigns it as variable
4. The innermost "wrap" partial/section is rendered (because no more `f:render` children exist)
5. The second level "wrap" partial/section is rendered with the HTML from step 4 as content variable.
6. The outer level "wrap" partial/section is rendered with the HTML from step 5 as content variable.
7. The entire thing is output.
  • Loading branch information
NamelessCoder committed Oct 9, 2015
1 parent b660cf5 commit 454121c
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 27 deletions.
15 changes: 10 additions & 5 deletions src/ViewHelpers/RenderViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public function initializeArguments() {
$this->registerArgument('arguments', 'array', 'Array of variables to be transferred. Use {_all} for all variables', FALSE, array());
$this->registerArgument('optional', 'boolean', 'If TRUE, considers the *section* optional. Partial never is.', FALSE, FALSE);
$this->registerArgument('default', 'mixed', 'Value (usually string) to be displayed if the section or partial does not exist', FALSE, NULL);
$this->registerArgument('contentAs', 'string', 'If used, renders the child content and adds it as a template variable with this name for use in the partial/section', FALSE, NULL);
}

/**
Expand All @@ -96,6 +97,13 @@ public function render() {
$partial = $this->arguments['partial'];
$arguments = (array) $this->arguments['arguments'];
$optional = (boolean) $this->arguments['optional'];
$contentAs = $this->arguments['contentAs'];
$tagContent = $this->renderChildren();

if ($contentAs !== NULL) {
$arguments[$contentAs] = $tagContent;
}

$content = '';
if ($partial !== NULL) {
$content = $this->viewHelperVariableContainer->getView()->renderPartial($partial, $section, $arguments, $optional);
Expand All @@ -105,11 +113,8 @@ public function render() {
// Replace empty content with default value. If default is
// not set, NULL is returned and cast to a new, empty string
// outside of this ViewHelper.
if ('' === $content) {
$content = $this->arguments['default'];
if (NULL === $content) {
$content = $this->renderChildren();
}
if ($content === '') {
$content = isset($this->arguments['default']) ? $this->arguments['default'] : $tagContent;
}
return $content;
}
Expand Down
102 changes: 80 additions & 22 deletions tests/Unit/ViewHelpers/RenderViewHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

use TYPO3Fluid\Fluid\ViewHelpers\RenderViewHelper;
use TYPO3Fluid\Fluid\View\TemplateView;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContext;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperVariableContainer;

Expand All @@ -15,6 +16,31 @@
*/
class RenderViewHelperTest extends ViewHelperBaseTestcase {

/**
* @var RenderViewHelper
*/
protected $subject;

/**
* @var TemplateView
*/
protected $view;

/**
* @return void
*/
public function setUp() {
$this->subject = $this->getMock('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', array('renderChildren'));
$renderingContext = new RenderingContext();
$paths = $this->getMock('TYPO3Fluid\\Fluid\\View\\TemplatePaths', array('sanitizePath'));
$paths->expects($this->any())->method('sanitizePath')->willReturnArgument(0);
$viewHelperVariableContainer = new ViewHelperVariableContainer();
$this->view = $this->getMock('TYPO3Fluid\\Fluid\\View\\TemplateView', array('renderPartial', 'renderSection'), array($paths, $renderingContext));
$viewHelperVariableContainer->setView($this->view);
$renderingContext->injectViewHelperVariableContainer($viewHelperVariableContainer);
$this->subject->setRenderingContext($renderingContext);
}

/**
* @test
*/
Expand All @@ -24,7 +50,8 @@ public function testInitializeArgumentsRegistersExpectedArguments() {
$instance->expects($this->at(1))->method('registerArgument')->with('partial', 'string', $this->anything(), FALSE, NULL);
$instance->expects($this->at(2))->method('registerArgument')->with('arguments', 'array', $this->anything(), FALSE, array());
$instance->expects($this->at(3))->method('registerArgument')->with('optional', 'boolean', $this->anything(), FALSE, FALSE);
$instance->expects($this->at(4))->method('registerArgument')->with('default', 'mixed', $this->anything(), FALSE, FALSE);
$instance->expects($this->at(4))->method('registerArgument')->with('default', 'mixed', $this->anything(), FALSE, NULL);
$instance->expects($this->at(5))->method('registerArgument')->with('contentAs', 'string', $this->anything(), FALSE, NULL);
$instance->initializeArguments();
}

Expand All @@ -35,24 +62,12 @@ public function testInitializeArgumentsRegistersExpectedArguments() {
* @param string|NULL $expectedViewMethod
*/
public function testRender(array $arguments, $expectedViewMethod) {
if ($expectedViewMethod) {
$methods = array($expectedViewMethod);
} else {
$methods = array('renderPartial', 'renderSection');
if ($expectedViewMethod !== NULL) {
$this->view->expects($this->once())->method($expectedViewMethod)->willReturn('');
}
$instance = $this->getMock('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', array('renderChildren'));
$instance->expects($this->any())->method('renderChildren')->willReturn(NULL);
$renderingContext = new RenderingContext();
$paths = $this->getMock('TYPO3Fluid\\Fluid\\View\\TemplatePaths', array('sanitizePath'));
$paths->expects($this->any())->method('sanitizePath')->willReturnArgument(0);
$viewHelperVariableContainer = new ViewHelperVariableContainer();
$view = $this->getMock('TYPO3Fluid\\Fluid\\View\\TemplateView', $methods, array($paths, $renderingContext));
$viewHelperVariableContainer->setView($view);
$renderingContext->injectViewHelperVariableContainer($viewHelperVariableContainer);
$instance->setArguments($arguments);
$instance->setRenderingContext($renderingContext);
$instance->render();

$this->subject->expects($this->any())->method('renderChildren')->willReturn(NULL);
$this->subject->setArguments($arguments);
$this->subject->render();
}

/**
Expand All @@ -61,22 +76,65 @@ public function testRender(array $arguments, $expectedViewMethod) {
public function getRenderTestValues() {
return array(
array(
array('partial' => NULL, 'section' => NULL, 'arguments' => array(), 'optional' => FALSE, 'default' => NULL),
array('partial' => NULL, 'section' => NULL, 'arguments' => array(), 'optional' => FALSE, 'default' => NULL, 'contentAs' => NULL),
NULL
),
array(
array('partial' => 'foo-partial', 'section' => NULL, 'arguments' => array(), 'optional' => FALSE, 'default' => NULL),
array('partial' => 'foo-partial', 'section' => NULL, 'arguments' => array(), 'optional' => FALSE, 'default' => NULL, 'contentAs' => NULL),
'renderPartial'
),
array(
array('partial' => 'foo-partial', 'section' => 'foo-section', 'arguments' => array(), 'optional' => FALSE, 'default' => NULL),
array('partial' => 'foo-partial', 'section' => 'foo-section', 'arguments' => array(), 'optional' => FALSE, 'default' => NULL, 'contentAs' => NULL),
'renderPartial'
),
array(
array('partial' => NULL, 'section' => 'foo-section', 'arguments' => array(), 'optional' => FALSE, 'default' => NULL),
array('partial' => NULL, 'section' => 'foo-section', 'arguments' => array(), 'optional' => FALSE, 'default' => NULL, 'contentAs' => NULL),
'renderSection'
),
);
}

/**
* @test
*/
public function testRenderWithDefautReturnsDefaultIfContentEmpty() {
$this->view->expects($this->once())->method('renderPartial')->willReturn('');
$this->subject->expects($this->any())->method('renderChildren')->willReturn(NULL);
$this->subject->setArguments(
array(
'partial' => 'test',
'section' => NULL,
'arguments' => array(),
'optional' => TRUE,
'default' => 'default-foobar',
'contentAs' => NULL
)
);
$output = $this->subject->render();
$this->assertEquals('default-foobar', $output);
}

/**
* @test
*/
public function testRenderSupportsContentAs() {
$variables = array('foo' => 'bar', 'foobar' => 'tagcontent-foobar');
$this->view->expects($this->once())->method('renderPartial')->with('test1', 'test2', $variables, TRUE)->willReturn('baz');
$this->subject->expects($this->any())->method('renderChildren')->willReturn('tagcontent-foobar');
$this->subject->setArguments(
array(
'partial' => 'test1',
'section' => 'test2',
'arguments' => array(
'foo' => 'bar'
),
'optional' => TRUE,
'default' => NULL,
'contentAs' => 'foobar'
)
);
$output = $this->subject->render();
$this->assertEquals('baz', $output);
}

}

0 comments on commit 454121c

Please sign in to comment.