https://www.drupal.org/u/garphy
@simonmorvan
Cycles de développement différents.
Drupal 7, Drupal Commerce, AngularJS
https://www.patrickroger.com
Drupal 7, Node.js, AngularJS
https://www.entrainement-athle.fr
Drupal 8, Angular 4, ThreeJS
https://www.icilalune.com
L'équation headless :
un projet = deux projets
(voire plus)
#1
Pour Drupal 7 : Services (& Services Entity)
ou RestWS
/node/3?_format=json
{
"nid": [
{
"value": 3
}
],
"uuid": [
{
"value": "a75a24f1-241a-4e77-9e2e-903e5f5f8563"
}
],
"vid": [
{
"value": 3
}
],
"langcode": [
{
"value": "en"
}
],
"type": [
{
"target_id": "container",
"target_type": "node_type",
"target_uuid": "1e009f03-6fc7-456f-8ce6-e2d911860b59"
}
],
"revision_timestamp": [
{
"value": "2017-09-14T14:25:09+00:00",
"format": "Y-m-d\\TH:i:sP"
}
],
"revision_uid": [
{
"target_id": 1,
"target_type": "user",
"target_uuid": "a3015686-d17a-44db-8231-c4a77fab44b9",
"url": "/user/1"
}
],
"revision_log": [],
"status": [
{
"value": true
}
],
"title": [
{
"value": "test"
}
],
"uid": [
{
"target_id": 1,
"target_type": "user",
"target_uuid": "a3015686-d17a-44db-8231-c4a77fab44b9",
"url": "/user/1"
}
],
"created": [
{
"value": "2017-09-14T14:25:09+00:00",
"format": "Y-m-d\\TH:i:sP"
}
],
"changed": [
{
"value": "2017-09-14T14:25:09+00:00",
"format": "Y-m-d\\TH:i:sP"
}
],
"promote": [
{
"value": false
}
],
"sticky": [
{
"value": false
}
],
"default_langcode": [
{
"value": true
}
],
"revision_translation_affected": [
{
"value": true
}
],
"container_children": [
{
"target_id": 5,
"target_type": "node",
"target_uuid": "dadef8de-ed44-47d1-85cf-0d20c05475f2",
"url": "/node/5"
},
{
"target_id": 7,
"target_type": "node",
"target_uuid": "7ede38bd-0a69-4877-98b4-bea441ddef5b",
"url": "/node/7"
}
],
"content_translation_source": [
{
"value": "und"
}
],
"content_translation_outdated": [
{
"value": false
}
],
"body": [],
"field_content_parent": [],
"field_content_weight": [],
"field_labels": [],
"field_properties": []
}
GET /rest/node?_format=json
A query language for your API
http://graphql.org/
https://www.drupal.org/project/graphql
Beta!
Query
{
hero {
name
height
mass
}
}
Result
{
"hero": {
"name": "Luke Skywalker",
"height": 1.72,
"mass": 77
}
}
A specification for building APIs in JSON
«your anti-bikeshedding tool»
http://jsonapi.org/
https://www.drupal.org/project/jsonapi
node--article
/jsonapi/node/article
https://www.drupal.org/node/2886540
/jsonapi/node/article/42
{
"data": {
"type": "node--article",
"id": "42",
"attributes": {
"title": "First node",
"created": 2147483647
},
"relationships": {
"author": {
"data": { "type": "user--user", "id": "9" }
}
},
}
}
/jsonapi/node/article
{
"data": [
{
"type": "node--article",
"id": "42",
"attributes": {...}
},
{
"type": "node--article",
"id": "84",
"attributes": {...}
}
]
}
/api/node/product/4?include=field_category
{
"data": {
"id": 4,
"type": "node--product",
"attributes": ...,
"relationships": {
"field_category":{
"data":{
"type":"taxonomy_term--category",
"id":"3"
}
}
}
},
"included": [{
"id": 3,
"type": "taxonomy_term--category",
"attributes": {
"name": "Drupalcamp goodies",
...
}
}]
}
GET /jsonapi/node/article?
filter[published][condition][path]=status
&filter[published][condition][value]=1
&filter[published][condition][operator]=%3D // URL encoded "="
GET /jsonapi/node/product?
filter[published][condition][path]=field_category.id
&filter[published][condition][value]=42
&filter[published][condition][operator]=%3D // URL encoded "="
https://www.drupal.org/docs/8/modules/json-api
loadNews(){
this.http.get('/jsonapi/node/article?sort=-created')
.subscribe(result => {
this.nodes = result.data;
});
}
<ul>
<li *ngFor="let node of nodes" (click)="loadNode(node.id)">
{{node.attributes?.title}}
</li>
</ul>
loadNode(id){
this.http.get(['/jsonapi/node/article',id].join('/')])
.subscribe(result => {
this.node = result.data;
});
}
<div>
<h1>{{node?.attributes?.title}}</h1>
<div [innerHTML]="node?.body?.value">
</div>
</div>
http://www.contentacms.org/
https://github.com/acquia/reservoir
⭬ JSON API
#1
#2
<html>
<body>
<script src="app.js"></script>
</body>
</html>
Chaque contenu devrait avoir sa propre URL
https://www.escapefactory.fr/#!/fr/news/halloween
<a href="#!/fr/bowling/20-pistes-de-bowling">Bowling</a>
https://www.icilalune.com/fr/articles
history.pushState({}, "Contact", "/fr/contact")
const appRoutes: Routes = [
{ path: 'news/:id', component: NewsDetailComponent },
{ path: 'news', component: NewsListComponent },
{ path: '', redirectTo: '/news' },
{ path: '**', component: PageNotFoundComponent }
];
Fonctionnement par motif
L'URL désigne le contenu.
L'URL doit être stable.
L'URL est une propriété du contenu.
https://www.icilalune.com/fr/articles/2017/04/objectif-montreal
https://www.icilalune.com/fr/breaking-news
Pour Angular et React : UI-Router
https://ui-router.github.io/
export const STATES: any[] = [
{
name: 'front',
component: FrontPageComponent,
params: {
id: {
type: 'string'
}
}
},
{
name: 'newsList',
component: NewsListComponent
},
{
name: 'newsDetails',
component: NewsDetailComponent,
params: {
id: {
type: 'string'
}
},
}]
https://backend.icilalune.com/api/path?path=/fr/articles/2017/04/objectif-montreal
Module Services
$provided_path = $request->query->get('path');
$provided_path_request = Request::create($provided_path);
$route = $router->matchRequest($provided_path_request);
if(preg_match('/^entity\.([a-zA-Z0-9_]+)\.canonical$/', $route['_route'])){
$entity_key = preg_replace('/^entity\.([a-zA-Z0-9_]+)\.canonical$/', '\1',
$route['_route']);
$entity = $route[$entity_key];
$result['entity'] = [
'type' => $entity->getEntityTypeId(),
'id' => $entity->id(),
'uuid' => $entity->uuid(),
'bundle' => $entity->bundle(),
];
}
https://backend.icilalune.com/api/path?path=/fr/articles/2017/04/objectif-montreal
{
"language":"fr",
"frontPage":false,
"entity":{
"type":"node",
"id":"33",
"uuid":"4affd605-30ee-4169-972c-b4eeb8e0cb89",
"bundle":"article",
},
}
resolvePath(path:string){
httpClient.get('/api/path', {
params: new HttpParams().set('path', path),
headers:new HttpHeaders({'Accept':'application/json'})
}).map(pathInfo => {
if (pathInfo.frontPage) {
return {
name: 'front',
params: {
id: pathInfo.entity.uuid
}
};
}
if (pathInfo.entity && pathInfo.entity.type === 'node') {
switch (pathInfo.entity.bundle) {
case 'article':
return {
name: 'newsListDetails',
params: {
id: pathInfo.entity.uuid
}
};
}
}
return {'name': '404'};
}).subscribe(state => {
this.uiRouter.stateService.go(state.name, state.params);
});
}
https://www.drupal.org/project/services_path
#2
#3
A partir de l'URL
$ curl http://www.headlessisfun.com/
$ curl http://www.headlessisfun.com/about
$ curl http://www.headlessisfun.com/article/42
<html>
<body>
<script src="app.js"></script>
</body>
</html>
https://github.com/prerender/prerender
https://www.icilalune.com/?_escaped_fragment_
Pour les autres bots : User-agent
Level #3