Save 37% off PRO during our Black Friday Sale! »

Content-driven Applications with Neos (English)

2a244c5ed94d92d288444604360a919a?s=47 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.

2a244c5ed94d92d288444604360a919a?s=128

hlubek

September 03, 2015
Tweet

Transcript

  1. Content-driven Applications creative webprojects. powered by Neos & Flow

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

    2008
  3. Christopher Hlubek CEO / Head of Development team member since

    2008 e.g. Content Editing, Content Rendering, TypoScript/Eel/ FlowQuery, Content Cache, Content Dimensions
  4. Website or Webapp?

  5. Website

  6. Website

  7. Website

  8. Website Server / Client / Ajax

  9. Website Server / Client / Ajax

  10. Website

  11. Website

  12. Website?

  13. Website?

  14. Website???

  15. Turning a website to an app with Neos

  16. Content Nodes Level 0

  17. Product List Node Type

  18. Product Node Type

  19. 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
  20. 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
  21. Product List TypoScript prototype(Demo.ContentApps:Product.List) {
 products = ContentCollection {
 nodePath

    = 'products'
 }
 } Demo.ContentApp/Resources/Private/TypoScript/NodeTypes/Product.ts2
  22. None
  23. 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
  24. 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
  25. 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
  26. None
  27. None
  28. None
  29. None
  30. Client-side Filter

  31. Done.

  32. Product details?

  33. Document Nodes Level 1

  34. Product Node Type Configuration 'Demo.ContentApps:Product':
 superTypes: ['TYPO3.Neos:Document']
 ... Demo.ContentApp/Configuration/NodeTypes.yaml

  35. Product Node Type Configuration 'Demo.ContentApps:Product':
 superTypes: ['TYPO3.Neos:Document']
 ... Demo.ContentApp/Configuration/NodeTypes.yaml

  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. None
  44. None
  45. None
  46. TypoScript your App Level 2

  47. Content Repository TypoScript Expression Language FlowQuery Neos Fluid Flow Custom

    Packages …
  48. Content Repository TypoScript Expression Language FlowQuery Neos Fluid Flow Custom

    Packages … TypoScript
  49. TypoScript = View Integration

  50. Elasticsearch Integration

  51. Searching and Filtering Nodes 
 ${Search.query(site).nodeType('Demo.ContentApps:Product').exactMatch('feature', feature).execute()}

  52. Rendering content depending on the request (e.g. query parameters)

  53. 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'
 }
  54. Extensible via TypoScript objects FlowQuery operations Eel helpers

  55. Pure TypoScript?

  56. Example: Product builder

  57. Requirements

  58. Requirements Display steps

  59. Requirements Display steps Form fields with selection

  60. Requirements Display steps Form fields with selection Pricing

  61. Requirements Display steps Form fields with selection Pricing Definition of

    dependencies
  62. Requirements Display steps Form fields with selection Pricing Definition of

    dependencies Images / text for information
  63. Requirements Display steps Form fields with selection Pricing Definition of

    dependencies Images / text for information
  64. Product Builder Node Types

  65. Product Step Builder Node Types

  66. Product Step Builder Node Types Step in the build

  67. Product Step Builder Node Types Element Step in the build

  68. Product Step Builder Node Types Element Step in the build

    Form field (different types)
  69. Product Step Builder Node Types Element Option Step in the

    build Form field (different types)
  70. Product Step Builder Node Types Element Option Step in the

    build Form field (different types) Selectable option
  71. Variant 1: JavaScript App

  72. Exposing the data

  73. Exposing the data Content Rendering JSON

  74. Exposing the data Content Rendering JSON Controller in Flow Package

  75. Implementation of selection

  76. Implementation of selection Controller for persistence

  77. Implementation of selection Controller for persistence Calculation of pricing /

    rules in JS
  78. Variant 2: Server-side flow

  79. Plugins Level 3

  80. Content Repository TypoScript Expression Language FlowQuery Neos Fluid Flow Custom

    Packages …
  81. Content Repository TypoScript Expression Language FlowQuery Neos Fluid Flow Custom

    Packages … TypoScript
  82. Content Repository TypoScript Expression Language FlowQuery Neos Fluid Flow Custom

    Packages … Custom Packages TypoScript
  83. Integration of any Controller

  84. 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
  85. 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!
  86. 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
  87. 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
  88. 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
  89. 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
  90. 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
  91. 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
  92. 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
  93. 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
  94. 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!
  95. ?

  96. Neos Frontend Rendering

  97. Neos Frontend Rendering Neos TypoScript View

  98. Neos Frontend Rendering Neos TypoScript View Build Plugin Controller

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

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

    TypoScript View Neos Content Rendering
  101. Neos Frontend Rendering Neos TypoScript View Build Plugin Controller Plugin

    TypoScript View Neos Content Rendering Plugin Template
  102. Content-driven Apps

  103. Small amount of code

  104. Content drives logic

  105. Mixture of data and content

  106. Benefits of the Content Repository

  107. Benefits of the Content Repository Workspaces

  108. Benefits of the Content Repository Workspaces Dimensions

  109. Benefits of the Content Repository Workspaces Dimensions Light-weight schema

  110. Integration of entities is possible

  111. Questions? creative webprojects. Christopher Hlubek @hlubek

  112. Thank you. Christopher Hlubek @hlubek creative webprojects.