Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Content-driven Applications with Neos (English)

hlubek
September 03, 2015

Content-driven Applications with Neos (English)

Meet Neos, Hamburg 2015 - How can a website be turned to an application with Neos? Many requirements can be implemented without much code by using nodes (content) in Neos.

hlubek

September 03, 2015
Tweet

More Decks by hlubek

Other Decks in Programming

Transcript

  1. Christopher Hlubek CEO / Head of Development team member since

    2008 e.g. Content Editing, Content Rendering, TypoScript/Eel/ FlowQuery, Content Cache, Content Dimensions
  2. Product List Node Type Configuration 'Demo.ContentApps:Product.List':
 superTypes: ['TYPO3.Neos:Content']
 childNodes:
 'products':


    type: 'TYPO3.Neos:ContentCollection'
 constraints:
 nodeTypes:
 'Demo.ContentApps:Product': TRUE
 '*': FALSE
 ui:
 label: 'Product List'
 icon: 'icon-asterisk'
 inlineEditable: true
 Demo.ContentApp/Configuration/NodeTypes.yaml
  3. Product List Node Type Template <section>
 <div class="container">
 <div class="row">


    <div class="col-md-12">
 <div class="row">
 {products -> f:format.raw()}
 </div>
 </div>
 </div>
 </div>
 </section> Demo.ContentApp/Resources/Private/Templates/NodeTypes/Product.List.html
  4. Product List TypoScript prototype(Demo.ContentApps:Product.List) {
 products = ContentCollection {
 nodePath

    = 'products'
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  5. Product Node Type Configuration 'Demo.ContentApps:Product':
 superTypes: ['TYPO3.Neos:Content']
 childNodes:
 'teaserimages':
 type:

    'TYPO3.Neos:ContentCollection'
 constraints:
 nodeTypes:
 'TYPO3.Neos.NodeTypes:Image': TRUE
 '*': FALSE
 ui:
 label: 'Product'
 icon: 'icon-asterisk'
 inspector:
 groups:
 'general':
 label: 'Properties for product'
 position: 10
 properties:
 'title':
 type: string
 ui:
 label: 'Title'
 inlineEditable: true
 aloha:
 placeholder: '<span>Insert title</span>'
 'image':
 type: 'TYPO3\Media\Domain\Model\ImageInterface'
 ui:
 label: 'Image'
 reloadIfChanged: TRUE
 inspector:
 group: 'general'
 'size':
 type: integer
 ui:
 label: 'Size'
 reloadIfChanged: TRUE
 inspector:
 group: 'general' Demo.ContentApp/Configuration/NodeTypes.yaml
  6. Product Node Type Template {namespace neos=TYPO3\Neos\ViewHelpers}
 <div class="listing-item">
 <div class="overlay-container">


    {imageTag -> f:format.raw()}
 <a class="overlay-link" href="{popoverImageUri}">...</a>
 </div>
 <div class="container-fluid">
 {teaserimages -> f:format.raw()}
 </div>
 <div class="body">
 <h3>{neos:contentElement.editable(property: 'title')}</h3>
 <span class="product-size"><strong>Size:</strong> {size}m</span>
 </div>
 </div> Demo.ContentApp/Resources/Private/Templates/NodeTypes/Product.html
  7. Product TypoScript prototype(Demo.ContentApps:Product) {
 teaserimages = ContentCollection {
 attributes.class =

    'row'
 nodePath = 'teaserimages'
 
 prototype(TYPO3.Neos.NodeTypes:Image) {
 attributes.class = 'col-md-3'
 maximumWidth = 80
 maximumHeight = 80
 }
 }
 imageTag = ImageTag {
 maximumWidth = 400
 maximumHeight = 400
 asset = ${q(node).property('image')}
 }
 popoverImageUri = ImageUri {
 maximumWidth = 1200
 maximumHeight = 800
 asset = ${q(node).property('image')}
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  8. Product List Node Type Configuration 'Demo.ContentApps:Product.List':
 superTypes: ['TYPO3.Neos:Content']
 ui:
 label:

    'Product List'
 icon: 'icon-asterisk'
 inlineEditable: true
 Demo.ContentApp/Configuration/NodeTypes.yaml
  9. Product List TypoScript prototype(Demo.ContentApps:Product.List) {
 products = TYPO3.TypoScript:Collection {
 collection

    = ${q(site).find('[instanceof Demo.ContentApps:Product]')}
 itemName = 'node'
 itemRenderer = Demo.ContentApps:Product.Teaser
 }
 
 @cache {
 mode = 'cached'
 entryTags {
 1 = ${Caching.nodeTypeTag('Demo.ContentApps:Product')}
 }
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  10. Product List TypoScript prototype(Demo.ContentApps:Product.List) {
 products = TYPO3.TypoScript:Collection {
 collection

    = ${q(site).find('[instanceof Demo.ContentApps:Product]')}
 itemName = 'node'
 itemRenderer = Demo.ContentApps:Product.Teaser
 }
 
 @cache {
 mode = 'cached'
 entryTags {
 1 = ${Caching.nodeTypeTag('Demo.ContentApps:Product')}
 }
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  11. Product TypoScript prototype(PrimaryContent) {
 product {
 condition = ${q(node).is('[instanceof Demo.ContentApps:Product]')}


    renderer = Demo.ContentApps:Product
 }
 }
 
 prototype(Demo.ContentApps:Product) < prototype(TYPO3.Neos:Content) {
 teaserimages = ContentCollection {
 attributes.class = 'row'
 nodePath = 'teaserimages'
 
 prototype(TYPO3.Neos.NodeTypes:Image) {
 attributes.class = 'col-md-3'
 maximumWidth = 200
 maximumHeight = 200
 }
 }
 imageTag = ImageTag {
 maximumWidth = 800
 maximumHeight = 600
 asset = ${q(node).property('image')}
 }
 popoverImageUri = ImageUri {
 maximumWidth = 1200
 maximumHeight = 800
 asset = ${q(node).property('image')}
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  12. Product TypoScript prototype(PrimaryContent) {
 product {
 condition = ${q(node).is('[instanceof Demo.ContentApps:Product]')}


    renderer = Demo.ContentApps:Product
 }
 }
 
 prototype(Demo.ContentApps:Product) < prototype(TYPO3.Neos:Content) {
 teaserimages = ContentCollection {
 attributes.class = 'row'
 nodePath = 'teaserimages'
 
 prototype(TYPO3.Neos.NodeTypes:Image) {
 attributes.class = 'col-md-3'
 maximumWidth = 200
 maximumHeight = 200
 }
 }
 imageTag = ImageTag {
 maximumWidth = 800
 maximumHeight = 600
 asset = ${q(node).property('image')}
 }
 popoverImageUri = ImageUri {
 maximumWidth = 1200
 maximumHeight = 800
 asset = ${q(node).property('image')}
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  13. Product TypoScript prototype(PrimaryContent) {
 product {
 condition = ${q(node).is('[instanceof Demo.ContentApps:Product]')}


    renderer = Demo.ContentApps:Product
 }
 }
 
 prototype(Demo.ContentApps:Product) < prototype(TYPO3.Neos:Content) {
 teaserimages = ContentCollection {
 attributes.class = 'row'
 nodePath = 'teaserimages'
 
 prototype(TYPO3.Neos.NodeTypes:Image) {
 attributes.class = 'col-md-3'
 maximumWidth = 200
 maximumHeight = 200
 }
 }
 imageTag = ImageTag {
 maximumWidth = 800
 maximumHeight = 600
 asset = ${q(node).property('image')}
 }
 popoverImageUri = ImageUri {
 maximumWidth = 1200
 maximumHeight = 800
 asset = ${q(node).property('image')}
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  14. Product Teaser TypoScript 
 prototype(Demo.ContentApps:Product.Teaser) < prototype(Demo.ContentApps:Product) {
 templatePath =

    'resource://Demo.ContentApps/Private/Templates/NodeTypes/ Product.Teaser.html'
 
 imageTag = ImageTag {
 maximumWidth = 400
 maximumHeight = 400
 }
 
 teaserimages {
 prototype(TYPO3.Neos.NodeTypes:Image) {
 maximumWidth = 80
 maximumHeight = 80
 }
 }
 }
 Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  15. Output multiple formats (XML, JSON, …) prototype(TYPO3.Neos.Seo:XmlSitemap) < prototype(TYPO3.TypoScript:Http.Message) {


    doctype = '<?xml version="1.0" encoding="UTF-8"?>'
 httpResponseHead.headers.Content-Type = 'text/xml'
 
 body = Menu {
 root = ${site}
 entryLevel = 0
 maximumLevels = 999
 renderHiddenInIndex = TRUE
 templatePath = 'resource://TYPO3.Neos.Seo/Private/Templates/Page/XmlSiteMap.xml'
 startingPoint = ${site}
 
 @cache.entryTags.1 = ${'DescendantOf_' + this.startingPoint.identifier}
 }
 }
 
 root.xmlSitemap {
 condition = ${request.format == 'xml.sitemap'}
 type = 'TYPO3.Neos.Seo:XmlSitemap'
 }
  16. Product Step Builder Node Types Element Option Step in the

    build Form field (different types) Selectable option
  17. Build Plugin prototype(Demo.ContentApps:Product) < prototype(TYPO3.Neos:Content) {
 buildPlugin = Plugin {


    package = 'Demo.ContentApps'
 controller = 'Build'
 action = 'index'
 }
 ...
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  18. Build Plugin prototype(Demo.ContentApps:Product) < prototype(TYPO3.Neos:Content) {
 buildPlugin = Plugin {


    package = 'Demo.ContentApps'
 controller = 'Build'
 action = 'index'
 }
 ...
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2 Easy!
  19. Build Controller class BuildController extends \TYPO3\Flow\Mvc\Controller\ActionController {
 
 protected $defaultViewObjectName

    = 'Demo\ContentApps\View\PluginTypoScriptView';
 
 /**
 * Display a start page for the product
 */
 public function indexAction() {
 /** @var NodeInterface $node */
 $node = $this->request->getInternalArgument('__node');
 
 if (!$node->getNodeType()->isOfType('Demo.ContentApps:Product')) {
 throw new InvalidNodeException(sprintf('Build plugin needs node of type "Demo.ContentApps:Product", got "%s"', $node->getNodeType()->getName()), 1432633804);
 }
 
 $this->view->assign('node', $node);
 } ... } Demo\ContentApps\Controller\BuildController
  20. Build Controller class BuildController extends \TYPO3\Flow\Mvc\Controller\ActionController {
 
 protected $defaultViewObjectName

    = 'Demo\ContentApps\View\PluginTypoScriptView';
 
 /**
 * Display a start page for the product
 */
 public function indexAction() {
 /** @var NodeInterface $node */
 $node = $this->request->getInternalArgument('__node');
 
 if (!$node->getNodeType()->isOfType('Demo.ContentApps:Product')) {
 throw new InvalidNodeException(sprintf('Build plugin needs node of type "Demo.ContentApps:Product", got "%s"', $node->getNodeType()->getName()), 1432633804);
 }
 
 $this->view->assign('node', $node);
 } ... } Demo\ContentApps\Controller\BuildController Plugin uses TypoScript for view Currently custom code needed
  21. Build Controller class BuildController extends \TYPO3\Flow\Mvc\Controller\ActionController {
 
 protected $defaultViewObjectName

    = 'Demo\ContentApps\View\PluginTypoScriptView';
 
 /**
 * Display a start page for the product
 */
 public function indexAction() {
 /** @var NodeInterface $node */
 $node = $this->request->getInternalArgument('__node');
 
 if (!$node->getNodeType()->isOfType('Demo.ContentApps:Product')) {
 throw new InvalidNodeException(sprintf('Build plugin needs node of type "Demo.ContentApps:Product", got "%s"', $node->getNodeType()->getName()), 1432633804);
 }
 
 $this->view->assign('node', $node);
 } ... } Demo\ContentApps\Controller\BuildController Pass values via TypoScript to the plugin controller Plugin uses TypoScript for view Currently custom code needed
  22. Build Controller class BuildController extends \TYPO3\Flow\Mvc\Controller\ActionController {
 ...
 
 /**


    * @param NodeInterface $step
 * @param array $selections An array from element identifier to option identifier
 */
 public function selectAction(NodeInterface $step, array $selections = array()) {
 /** @var NodeInterface $node */
 $node = $this->request->getInternalArgument('__node');
 
 foreach ($selections as $elementIdentifier => $optionIdentifiers) {
 $element = $node->getContext()->getNodeByIdentifier($elementIdentifier);
 if (!is_array($optionIdentifiers)) {
 $optionIdentifiers = array($optionIdentifiers);
 }
 $optionNodes = array_map(function($optionIdentifier) use ($node) {return $node->getContext()->getNodeByIdentifier($optionIdentifier);}, $optionIdentifiers);
 $this->build->addSelection($step, $element, $optionNodes);
 }
 
 $fq = new \TYPO3\Eel\FlowQuery\FlowQuery(array($step));
 $nextStep = $fq->next('[instanceof Demo.ContentApps:Step]')->get(0);
 
 if ($nextStep === NULL) {
 $this->forward('finish');
 } else {
 $this->forward('step', NULL, NULL, array('step' => $nextStep));
 }
 } } Demo\ContentApps\Controller\BuildController
  23. Build Controller class BuildController extends \TYPO3\Flow\Mvc\Controller\ActionController {
 ...
 
 /**


    * @param NodeInterface $step
 * @param array $selections An array from element identifier to option identifier
 */
 public function selectAction(NodeInterface $step, array $selections = array()) {
 /** @var NodeInterface $node */
 $node = $this->request->getInternalArgument('__node');
 
 foreach ($selections as $elementIdentifier => $optionIdentifiers) {
 $element = $node->getContext()->getNodeByIdentifier($elementIdentifier);
 if (!is_array($optionIdentifiers)) {
 $optionIdentifiers = array($optionIdentifiers);
 }
 $optionNodes = array_map(function($optionIdentifier) use ($node) {return $node->getContext()->getNodeByIdentifier($optionIdentifier);}, $optionIdentifiers);
 $this->build->addSelection($step, $element, $optionNodes);
 }
 
 $fq = new \TYPO3\Eel\FlowQuery\FlowQuery(array($step));
 $nextStep = $fq->next('[instanceof Demo.ContentApps:Step]')->get(0);
 
 if ($nextStep === NULL) {
 $this->forward('finish');
 } else {
 $this->forward('step', NULL, NULL, array('step' => $nextStep));
 }
 } } Demo\ContentApps\Controller\BuildController Node as argument
  24. Build Controller class BuildController extends \TYPO3\Flow\Mvc\Controller\ActionController {
 ...
 
 /**


    * @param NodeInterface $step
 * @param array $selections An array from element identifier to option identifier
 */
 public function selectAction(NodeInterface $step, array $selections = array()) {
 /** @var NodeInterface $node */
 $node = $this->request->getInternalArgument('__node');
 
 foreach ($selections as $elementIdentifier => $optionIdentifiers) {
 $element = $node->getContext()->getNodeByIdentifier($elementIdentifier);
 if (!is_array($optionIdentifiers)) {
 $optionIdentifiers = array($optionIdentifiers);
 }
 $optionNodes = array_map(function($optionIdentifier) use ($node) {return $node->getContext()->getNodeByIdentifier($optionIdentifier);}, $optionIdentifiers);
 $this->build->addSelection($step, $element, $optionNodes);
 }
 
 $fq = new \TYPO3\Eel\FlowQuery\FlowQuery(array($step));
 $nextStep = $fq->next('[instanceof Demo.ContentApps:Step]')->get(0);
 
 if ($nextStep === NULL) {
 $this->forward('finish');
 } else {
 $this->forward('step', NULL, NULL, array('step' => $nextStep));
 }
 } } Demo\ContentApps\Controller\BuildController Node as argument Can use session
  25. Build Controller class BuildController extends \TYPO3\Flow\Mvc\Controller\ActionController {
 ...
 
 /**


    * @param NodeInterface $step
 * @param array $selections An array from element identifier to option identifier
 */
 public function selectAction(NodeInterface $step, array $selections = array()) {
 /** @var NodeInterface $node */
 $node = $this->request->getInternalArgument('__node');
 
 foreach ($selections as $elementIdentifier => $optionIdentifiers) {
 $element = $node->getContext()->getNodeByIdentifier($elementIdentifier);
 if (!is_array($optionIdentifiers)) {
 $optionIdentifiers = array($optionIdentifiers);
 }
 $optionNodes = array_map(function($optionIdentifier) use ($node) {return $node->getContext()->getNodeByIdentifier($optionIdentifier);}, $optionIdentifiers);
 $this->build->addSelection($step, $element, $optionNodes);
 }
 
 $fq = new \TYPO3\Eel\FlowQuery\FlowQuery(array($step));
 $nextStep = $fq->next('[instanceof Demo.ContentApps:Step]')->get(0);
 
 if ($nextStep === NULL) {
 $this->forward('finish');
 } else {
 $this->forward('step', NULL, NULL, array('step' => $nextStep));
 }
 } } Demo\ContentApps\Controller\BuildController Node as argument FlowQuery in PHP Can use session
  26. Plugin TypoScript View plugins.Build.index = TYPO3.TypoScript:Template {
 templatePath = 'resource://Demo.ContentApps/Private/Templates/Build/Index.html'


    
 node = ${node}
 firstStep = ${q(node).children('[instanceof Demo.ContentApps:Step]').first().get(0)}
 
 customerInformation = TYPO3.Neos:ContentCollection {
 nodePath = 'customerinformation'
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Plugin.ts2
  27. Plugin TypoScript View plugins.Build.index = TYPO3.TypoScript:Template {
 templatePath = 'resource://Demo.ContentApps/Private/Templates/Build/Index.html'


    
 node = ${node}
 firstStep = ${q(node).children('[instanceof Demo.ContentApps:Step]').first().get(0)}
 
 customerInformation = TYPO3.Neos:ContentCollection {
 nodePath = 'customerinformation'
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Plugin.ts2 Rendering of content out of the plugin!
  28. ?

  29. Neos Frontend Rendering Neos TypoScript View Build Plugin Controller Plugin

    TypoScript View Neos Content Rendering Plugin Template