Content-driven Applications mit Neos (German)

2a244c5ed94d92d288444604360a919a?s=47 hlubek
September 03, 2015

Content-driven Applications mit Neos (German)

Meet Neos, Hamburg 2015 - Wie kann mit Neos aus einer Website eine Applikation entstehen? Ohne viel Code können viele Anforderungen effizient mit Nodes (Content) implementiert werden.

2a244c5ed94d92d288444604360a919a?s=128

hlubek

September 03, 2015
Tweet

Transcript

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

  2. Christopher Hlubek Geschäftsführer / Leitung Entwicklung im Team seit 2008

  3. Christopher Hlubek Geschäftsführer / Leitung Entwicklung im Team seit 2008

    u.a. Content Editing, Content Rendering, TypoScript/Eel/ FlowQuery, Content Cache, Content Dimensions
  4. Website oder 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. Von der Website zur App mit Neos

  16. Content Nodes Level 0

  17. Product List Node Type

  18. Product Node Type

  19. Product List Node Type Konfiguration '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 Konfiguration '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-seitiger Filter

  31. Done.

  32. Produkt-Details?

  33. Document Nodes Level 1

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

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

  36. Product List Node Type Konfiguration '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. Nodes Suchen und Filtern 
 ${Search.query(site).nodeType('Demo.ContentApps:Product').exactMatch('feature', feature).execute()}

  52. Content Abhängig vom Request rendern

  53. Ausgabe unterschiedlicher Formate (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. Erweiterbar über TypoScript Objekte FlowQuery Operationen Eel Helper

  55. Nur mit TypoScript?

  56. Beispiel: Konfigurator

  57. Anforderungen

  58. Anforderungen Anzeige Schritte

  59. Anforderungen Anzeige Schritte Formularfelder mit Auswahl

  60. Anforderungen Anzeige Schritte Formularfelder mit Auswahl Abbildung von Preisen

  61. Anforderungen Anzeige Schritte Formularfelder mit Auswahl Abbildung von Preisen Definition

    von Abhängigkeiten
  62. Anforderungen Anzeige Schritte Formularfelder mit Auswahl Abbildung von Preisen Definition

    von Abhängigkeiten Bilder / Texte zur Erläuterung
  63. Anforderungen Anzeige Schritte Formularfelder mit Auswahl Abbildung von Preisen Definition

    von Abhängigkeiten Bilder / Texte zur Erläuterung
  64. Product Konfigurator Node Types

  65. Product Step Konfigurator Node Types

  66. Product Step Konfigurator Node Types Schritt des Konfigurators

  67. Product Step Konfigurator Node Types Element Schritt des Konfigurators

  68. Product Step Konfigurator Node Types Element Schritt des Konfigurators Formularfeld

    / Auswahl
  69. Product Step Konfigurator Node Types Element Option Schritt des Konfigurators

    Formularfeld / Auswahl
  70. Product Step Konfigurator Node Types Element Option Schritt des Konfigurators

    Formularfeld / Auswahl Auswahloption
  71. Variante 1: JavaScript App

  72. Bereitstellung der Daten

  73. Bereitstellung der Daten Content Rendering JSON

  74. Bereitstellung der Daten Content Rendering JSON Controller in Flow Package

  75. Abbildung der Auswahl

  76. Abbildung der Auswahl Controller zum Speichern

  77. Abbildung der Auswahl Controller zum Speichern Berechnung der Preise /

    Regeln in JS
  78. Variante 2: Server-seitiger Ablauf

  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 beliebiger Controller

  84. Konfigurator 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. Konfigurator 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. Konfigurator 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. Konfigurator 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 nutzt TypoScript für View Derzeit Custom
  88. Konfigurator 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 Übergabe von Werten via TypoScript Plugin nutzt TypoScript für View Derzeit Custom
  89. Konfigurator 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. Konfigurator 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 als Argument
  91. Konfigurator 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 als Argument Kann Session benutzen
  92. Konfigurator 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 als Argument FlowQuery auch in PHP Kann Session benutzen
  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 von Content aus dem 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. Wenig Code notwendig

  104. Content steuert Logik

  105. Mischung aus Daten und Content

  106. Vorteile des Content Repositories

  107. Vorteile des Content Repositories Workspaces

  108. Vorteile des Content Repositories Workspaces Dimensions

  109. Vorteile des Content Repositories Workspaces Dimensions Leichtgewichtiges Schema

  110. Integration von Entities möglich

  111. Fragen? creative webprojects. Christopher Hlubek @hlubek

  112. Danke. Christopher Hlubek @hlubek creative webprojects.