inspiring conference 2014 - node kingdom
DESCRIPTION
Basics about Structured Content and how your content inventory can help you configure TYPO3 Neos to have a proper Editor experienceTRANSCRIPT
Inspiring Conference 2014
TYPO3 NeosNode Kingdom
Inspiring Conference 2014
Dominique Feyer
Cofounder of ttree ltd + medialib.tv ltd
Inspiring Conference 2014
What’s nextShort introduction to Structured Content
Think about content
Your first custom Node Type
A bit more advanced example
Inspiring Conference 2014
Let’s start with some definitions
Inspiring Conference 2014
What’s a Kingdom ?A really nice place to live in
Where a king take care of each citizen
A really romantic vision, no ?
Inspiring Conference 2014
Do you see the magic ?
Inspiring Conference 2014
What’s a Node ?
A node is a small piece of content
Content is your first class citizen
Can be structured
Nodes can contain other node
Inspiring Conference 2014
Entity or NodeA different way to interact with content
In Neos, try to focus on Node
- Inline editing, Workspace, Content Dimension
- FlowQuery & EEL, Import / Export, …
!
Standard Doctrine Entity can be used for specific needs
Inspiring Conference 2014
We are not Page Centric
Inspiring Conference 2014
It’s not a Page Tree
It’s a Document Tree
Inspiring Conference 2014
A Page is just a specific Document
A document can be anything
Inspiring Conference 2014
When did you need a new Document TypeYou need an URL to access this content
- Customer
- Project Reference
- People
- Blog post, News, …
Inspiring Conference 2014
Do you remember your first jQuery plugin ?
That was maybe not « yours »
What about JavaScript internals ?
Inspiring Conference 2014
Keep your content cleanAs an editor
I need special formatting for some paragraph
Inspiring Conference 2014
Inspiring Conference 2014
Inspiring Conference 2014
How it’s done ?
A new Node Type based on the default Text Node Type
A small piece of TypoScript
Inspiring Conference 2014
Please defineNode TypeA Node Type define the structure of a node
A Node Type can have Super Type (inheritance)
The structure of a node can change
Inspiring Conference 2014
Add a Node Type'Ttree.OfficialWebsite:Teaser': superTypes: - 'TYPO3.Neos.NodeTypes:Text' ui: label: Teaser inspector: groups: theme: label: Theme position: 5 properties: reverse: type: boolean ui: label: 'Gray Theme' reloadIfChanged: true inspector: group: theme
Inspiring Conference 2014
A short TypoScript
prototype(Ttree.OfficialWebsite:Teaser) > prototype(Ttree.OfficialWebsite:Teaser) < prototype(TYPO3.Neos.NodeTypes:Text) { attributes.class = ${node.properties.reverse ? 'teaser teaser-reverse' : 'teaser'} }
Inspiring Conference 2014
A bit more advancedAs a client
I need to display a list of persons on my website
Each person has their own profile
I need to be able to insert person address and a link to the profile on any page
Inspiring Conference 2014
What’s a Person
Your first Abstract Node Type
'Ttree.InspiringConf:Schema.Person': abstract: true ui: inspector: groups: person: label: 'Person' position: 1 properties: personName: type: string ui: label: 'Name' reloadIfChanged: TRUE inspector: group: person
Inspiring Conference 2014
Your second Abstract Node Type
What’s a Postal Address
'Ttree.InspiringConf:Schema.PostalAddress': abstract: true ui: inspector: groups: postalAddress: label: 'Postal Address' position: 2 properties: postalAddressStreetAddress: type: string ui: label: 'Street Address' reloadIfChanged: TRUE inspector: group: postalAddress postalAddressPostalCode: type: string ui: label: 'Postal Code' reloadIfChanged: TRUE inspector: group: postalAddress postalAddressLocality: type: string ui: label: 'Locality' reloadIfChanged: TRUE
Inspiring Conference 2014
Document Node Type
Extend the TYPO3.Neos:Document
Extend your abstract nodes
Add a content collection
'Ttree.InspiringConf:Person': superTypes: - 'TYPO3.Neos:Document' - 'Ttree.InspiringConf:Schema.PostalAddress' - 'Ttree.InspiringConf:Schema.Person' ui: label: 'Person' icon: 'icon-user' group: inspiringCon childNodes: profile: type: 'TYPO3.Neos:ContentCollection'
Inspiring Conference 2014
TypoScriptprototype(TYPO3.Neos:PrimaryContent).TtreeInspiringConfPerson { condition = ${q(node).is('[instanceof Ttree.InspiringConf:Person]')} type = 'Ttree.InspiringConf:Person' @position = 'start' } prototype(Ttree.InspiringConf:Person) < prototype(TYPO3.TypoScript:Template) { templatePath = 'resource://Ttree.InspiringConf/Private/Templates/NodeTypes/Person.html' ! attributes = TYPO3.TypoScript:Attributes { class = 'person-profile-page' data-ttree-region = ${node.properties.postalAddressRegion} data-ttree-country = ${node.properties.postalAddressCountry} } ! personName = ${node.properties.personName} postalAddressStreetAddress = ${node.properties.postalAddressStreetAddress} postalAddressCountry = ${node.properties.postalAddressCountry} postalAddressPostalCode = ${node.properties.postalAddressPostalCode} postalAddressLocality = ${node.properties.postalAddressLocality} postalAddressRegion = ${node.properties.postalAddressRegion} …
Inspiring Conference 2014
You say PrimaryContent ?
Use it only one time in your page
Neos know where to render the main content
Don’t use it for your sidebar
prototype(TYPO3.Neos:PrimaryContent).TtreeInspiringConfPerson { condition = ${q(node).is('[instanceof Ttree.InspiringConf:Person]')} type = 'Ttree.InspiringConf:Person' @position = 'start' }
Inspiring Conference 2014
Let’s preparing the view …Will be rendered in your PrimaryContent area
!
prototype(Ttree.InspiringConf:Person) < prototype(TYPO3.TypoScript:Template) { templatePath = 'resource://Ttree.InspiringConf/Private/Templates/NodeTypes/Person.html' ! attributes = TYPO3.TypoScript:Attributes { class = 'person-profile-page' data-ttree-region = ${node.properties.postalAddressRegion} data-ttree-country = ${node.properties.postalAddressCountry} } ! personName = ${node.properties.personName} postalAddressStreetAddress = ${node.properties.postalAddressStreetAddress} postalAddressCountry = ${node.properties.postalAddressCountry} postalAddressPostalCode = ${node.properties.postalAddressPostalCode} postalAddressLocality = ${node.properties.postalAddressLocality} postalAddressRegion = ${node.properties.postalAddressRegion} ! profile = TYPO3.Neos:ContentCollection { nodePath = 'profile' } }
Inspiring Conference 2014
The Fluid template{namespace neos=TYPO3\Neos\ViewHelpers} <div{attributes -> f:format.raw()}> <div itemscope itemtype="http://schema.org/Person"> <h1 itemprop="name">{personName}</h1> <span itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> <span itemprop="streetAddress"> {postalAddressStreetAddress} </span> | <span itemprop="addressCountry">{postalAddressCountry}</span>, <span itemprop="postalCode">{postalAddressPostalCode}</span>, <span itemprop="addressLocality">{postalAddressLocality}</span>, <span itemprop="addressRegion">{postalAddressRegion}</span> </span> </div> ! <div class="customer-profile"> {profile -> f:format.raw()} </div> </div>
Inspiring Conference 2014
The final rendering
Inspiring Conference 2014
Link to the profile from any
page ?
Inspiring Conference 2014
Inspiring Conference 2014
What about a new node type ?As an editor
I need to insert on any page a contact address
Inspiring Conference 2014
Node Type'Ttree.InspiringConf:ContactAddress': superTypes: - 'TYPO3.Neos:Content' nodeLabelGenerator: 'Ttree\InspiringConf\Domain\Model\PersonNodeLabelGenerator' ui: label: 'Contact Address' group: 'inspiringCon' inspector: groups: document: label: 'Address' position: 1 properties: person: type: reference ui: label: 'Person' reloadIfChanged: true inspector: group: 'document' editorOptions: nodeTypes: - 'Ttree.InspiringConf:Person'
Inspiring Conference 2014
TypoScriptprototype(Ttree.InspiringConf:ContactAddress) > prototype(Ttree.InspiringConf:ContactAddress) < prototype(TYPO3.Neos:Content) { templatePath = 'resource://Ttree.InspiringConf/Private/Templates/NodeTypes/ContactAddress.html' ! person = ${node.properties.person} hasPerson = ${node.properties.person ? TRUE : FALSE} ! attributes = TYPO3.TypoScript:Attributes { class = 'person-profile-inline' style = 'background: #CCC; padding: 10px; margin-bottom: 10px;' data-ttree-region = ${this.person.properties.postalAddressRegion} data-ttree-country = ${this.person.properties.postalAddressCountry} } ! personName = ${this.person.properties.personName} postalAddressStreetAddress = ${this.person.properties.postalAddressStreetAddress} postalAddressCountry = ${this.person.properties.postalAddressCountry} postalAddressPostalCode = ${this.person.properties.postalAddressPostalCode} postalAddressLocality = ${this.person.properties.postalAddressLocality} postalAddressRegion = ${this.person.properties.postalAddressRegion} }
Inspiring Conference 2014
Template{namespace neos=TYPO3\Neos\ViewHelpers} <div{attributes -> f:format.raw()}> <f:if condition="{hasPerson}"> <f:then> <div itemscope itemtype="http://schema.org/Person"> <div style="font-weight: bold;" itemprop="name">{personName}</div> <div itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> <div itemprop="streetAddress"> {postalAddressStreetAddress} </div> <div> <span itemprop="addressCountry">{postalAddressCountry}</span> - <span itemprop="postalCode">{postalAddressPostalCode}</span> <span itemprop="addressLocality">{postalAddressLocality}</span> </div> </div> </div> </f:then> <f:else> <strong> Please select a Person<br/> in the Inspector </strong> </f:else> </f:if> </div>
Inspiring Conference 2014
Inspiring Conference 2014
nodeLabelGenerator: 'Ttree\InspiringConf\Domain\Model\PersonNodeLabelGenerator'
Custom node label, but why ?
Inspiring Conference 2014
class PersonNodeLabelGenerator extends DefaultNodeLabelGenerator { public function getLabel(AbstractNodeData $nodeData, $crop = TRUE) { if ($nodeData->hasProperty('person') === TRUE && $nodeData->getProperty('person')) { $label = 'Link to: ' . strip_tags($nodeData->getProperty('person')-> getProperty('personName')); } else { $label = parent::getLabel($nodeData, FALSE); } if ($crop === FALSE) { return $label; } ! $croppedLabel = \TYPO3\Flow\Utility\Unicode\Functions::substr($label, 0, NodeInterface::LABEL_MAXIMUM_CHARACTERS); return $croppedLabel . (strlen($croppedLabel) < strlen($label) ? ' …’ : ''); } }
Inspiring Conference 2014
class PersonNodeLabelGenerator extends DefaultNodeLabelGenerator { public function getLabel(AbstractNodeData $nodeData, $crop = TRUE) { if ($nodeData->hasProperty('person') === TRUE && $nodeData->getProperty('person')) { $label = 'Link to: ' . strip_tags($nodeData->getProperty('person')-> getProperty('personName')); } else { $label = parent::getLabel($nodeData, FALSE); } if ($crop === FALSE) { return $label; } ! $croppedLabel = \TYPO3\Flow\Utility\Unicode\Functions::substr($label, 0, NodeInterface::LABEL_MAXIMUM_CHARACTERS); return $croppedLabel . (strlen($croppedLabel) < strlen($label) ? ' …’ : ''); } }
Inspiring Conference 2014
class PersonNodeLabelGenerator extends DefaultNodeLabelGenerator { public function getLabel(AbstractNodeData $nodeData, $crop = TRUE) { if ($nodeData->hasProperty('person') === TRUE && $nodeData->getProperty('person')) { $label = 'Link to: ' . strip_tags($nodeData->getProperty('person')-> getProperty('personName')); } else { $label = parent::getLabel($nodeData, FALSE); } if ($crop === FALSE) { return $label; } ! $croppedLabel = \TYPO3\Flow\Utility\Unicode\Functions::substr($label, 0,NodeInterface::LABEL_MAXIMUM_CHARACTERS); return $croppedLabel . (strlen($croppedLabel) < strlen($label) ? ' …’ : ''); } }
Inspiring Conference 2014
Lazy mode pseudo live
demonstration
Inspiring Conference 2014
Inspiring Conference 2014
We, as a community, are currently redefining the future of CMS
Thanks
Inspiring Conference 2014
Fork me on Github https://github.com/dfeyer/Ttree.InspiringConf
Follow us @ttreeagency
Follow me @dfeyer
Questions