1 of 45

by Jani Tarvainen, Antonin Savoie & Baptiste Leulliette

2 of 45

Summary

  • Presentation of the eZPlatform REST API
  • Extending the REST API : let’s perform a search
  • How to use VueJS
  • Let’s replicate the eZWebSummerCamp website !

3 of 45

Doc is love... Doc is life !

https://github.com/ezsystems/ezpublish-kernel/blob/master/doc/specifications/rest/REST-API-V2.rst

4 of 45

Old school or new school?

Which side are you?!

5 of 45

Let’s consume the API...

What do we need here ?�

HTTP header ‘Accept’

REST prefix : /api/ezp/v2

6 of 45

Media types and endpoints

<Root media-type="application/vnd.ez.api.Root+xml">� <content media-type="" href="/api/ezp/v2/content/objects"/>� <contentByRemoteId media-type="" href="/api/ezp/v2/content/objects{?remoteId}"/>� <contentTypes media-type="application/vnd.ez.api.ContentTypeInfoList+xml" href="/api/ezp/v2/content/types"/>� <contentTypeByIdentifier media-type="" href="/api/ezp/v2/content/types{?identifier}"/>� <contentTypeGroups media-type="application/vnd.ez.api.ContentTypeGroupList+xml" href="/api/ezp/v2/content/typegroups"/>� <contentTypeGroupByIdentifier media-type="" href="/api/ezp/v2/content/typegroups{?identifier}"/>� <users media-type="application/vnd.ez.api.UserRefList+xml" href="/api/ezp/v2/user/users"/>� <roles media-type="application/vnd.ez.api.RoleList+xml" href="/api/ezp/v2/user/roles"/>� <rootLocation media-type="application/vnd.ez.api.Location+xml" href="/api/ezp/v2/content/locations/1/2"/>� <rootUserGroup media-type="application/vnd.ez.api.UserGroup+xml" href="/api/ezp/v2/user/groups/1/5"/>� <rootMediaFolder media-type="application/vnd.ez.api.Location+xml" href="/api/ezp/v2/content/locations/1/43"/>� <locationByRemoteId media-type="" href="/api/ezp/v2/content/locations{?remoteId}"/>� <locationByPath media-type="" href="/api/ezp/v2/content/locations{?locationPath}"/>� <trash media-type="application/vnd.ez.api.Trash+xml" href="/api/ezp/v2/content/trash"/>� <sections media-type="application/vnd.ez.api.SectionList+xml" href="/api/ezp/v2/content/sections"/>� <views media-type="application/vnd.ez.api.RefList+xml" href="/api/ezp/v2/views"/>� <objectStateGroups media-type="application/vnd.ez.api.ObjectStateGroupList+xml" href="/api/ezp/v2/content/objectstategroups"/>� <objectStates media-type="application/vnd.ez.api.ObjectStateList+xml" href="/api/ezp/v2/content/objectstategroups/{objectStateGroupId}/objectstates"/>� <globalUrlAliases media-type="application/vnd.ez.api.UrlAliasRefList+xml" href="/api/ezp/v2/content/urlaliases"/>� <urlWildcards media-type="application/vnd.ez.api.UrlWildcardList+xml" href="/api/ezp/v2/content/urlwildcards"/>� <createSession media-type="application/vnd.ez.api.UserSession+xml" href="/api/ezp/v2/user/sessions"/>� <refreshSession media-type="application/vnd.ez.api.UserSession+xml" href="/api/ezp/v2/user/sessions/{sessionId}/refresh"/>�</Root>

7 of 45

Let’s consume the API : ‘Accept’ HTTP header

  • Most important part to handle in the Rest API
  • How to construct it :

application/vnd.ez.api.

[Media-Type]

+[Content-Type]

8 of 45

Let’s consume the REST API: Time to try!

curl ezrestapi.websc/api/ezp/v2/content/objects/1

<Content media-type="application/vnd.ez.api.ContentInfo+xml" href="/api/ezp/v2/content/objects/1" remoteId="9459d3c29e15006e45197295722c7ade" id="1">

<ContentType media-type="application/vnd.ez.api.ContentType+xml" href="/api/ezp/v2/content/types/1"/>

<Name>eZ Platform</Name>

<Versions media-type="application/vnd.ez.api.VersionList+xml" href="/api/ezp/v2/content/objects/1/versions"/>

<CurrentVersion media-type="application/vnd.ez.api.Version+xml" href="/api/ezp/v2/content/objects/1/currentversion"/>

<Section media-type="application/vnd.ez.api.Section+xml" href="/api/ezp/v2/content/sections/1"/>

<MainLocation media-type="application/vnd.ez.api.Location+xml" href="/api/ezp/v2/content/locations/1/2"/>

<Locations media-type="application/vnd.ez.api.LocationList+xml" href="/api/ezp/v2/content/objects/1/locations"/>

<Owner media-type="application/vnd.ez.api.User+xml" href="/api/ezp/v2/user/users/14"/>

<lastModificationDate>2015-11-30T14:10:46+01:00</lastModificationDate>

<publishedDate>2015-11-30T14:10:46+01:00</publishedDate>

<mainLanguageCode>eng-GB</mainLanguageCode>

<currentVersionNo>9</currentVersionNo>

<alwaysAvailable>true</alwaysAvailable>

<ObjectStates media-type="application/vnd.ez.api.ContentObjectStates+xml" href="/api/ezp/v2/content/objects/1/objectstates"/>

</Content>

9 of 45

Let’s consume the REST API: Time to try!

curl ezrestapi.websc/api/ezp/v2/content/objects/1

-H "Accept: application/vnd.ez.api.ContentInfo+json"

"Content": {

"_media-type": "application/vnd.ez.api.ContentInfo+json",

"_href": "/api/ezp/v2/content/objects/1",

"_remoteId": "9459d3c29e15006e45197295722c7ade",

"_id": 1,

"ContentType": {

"_media-type": "application/vnd.ez.api.ContentType+json",

"_href": "/api/ezp/v2/content/types/1"

},

"Name": "eZ Platform",

"Versions": {

"_media-type": "application/vnd.ez.api.VersionList+json",

"_href": "/api/ezp/v2/content/objects/1/versions"

},

"CurrentVersion": {

"_media-type": "application/vnd.ez.api.Version+json",

"_href": "/api/ezp/v2/content/objects/1/currentversion"

},

"Section": {

"_media-type": "application/vnd.ez.api.Section+json",

"_href": "/api/ezp/v2/content/sections/1"

},

"MainLocation": {

"_media-type": "application/vnd.ez.api.Location+json",

"_href": "/api/ezp/v2/content/locations/1/2"

},

"Locations": {

"_media-type": "application/vnd.ez.api.LocationList+json",

"_href": "/api/ezp/v2/content/objects/1/locations"

},

"Owner": {

"_media-type": "application/vnd.ez.api.User+json",

"_href": "/api/ezp/v2/user/users/14"

},

"lastModificationDate": "2015-11-30T14:10:46+01:00",

"publishedDate": "2015-11-30T14:10:46+01:00",

"mainLanguageCode": "eng-GB",

"currentVersionNo": 9,

"alwaysAvailable": true

}

There is any content fields

10 of 45

Let’s consume the REST API: Time to try!

curl ezrestapi.websc/api/ezp/v2/content/objects/1

-H "Accept: application/vnd.ez.api.Content+json"

"CurrentVersion": {

...,

"Version": {

…,

"VersionInfo": {

"id": 506,

"versionNo": 9,

"status": "PUBLISHED",

"modificationDate": "2015-11-30T14:10:46+01:00",

"creationDate": "2015-11-30T14:10:45+01:00",

"initialLanguageCode": "eng-GB",

"languageCodes": "eng-GB",

"Content": {

"_media-type": "application/vnd.ez.api.ContentInfo+json",

"_href": "/api/ezp/v2/content/objects/1"

}

},

"Fields": {

"field": [

{

"id": 1,

"fieldDefinitionIdentifier": "name",

"languageCode": "eng-GB",

"fieldValue": "Welcome to eZ Platform"

},

Here we go !

We have our fields !

11 of 45

12 of 45

Install the fixtures

php app/console restapi:generate-fixtures

git fetch

git checkout exercice-extend-API

13 of 45

Extend the Rest API : Register the endpoint

  • Create a routing_rest.yml in your Bundle/Resources/config

  • Go to default routing.yml and make your routing_rest.yml using the Rest API prefix

custom_rest_routes:� resource: "@AppBundle/Resources/config/routing_rest.yml"� prefix: "%ezpublish_rest.path_prefix%"

search_by_category:� path: /search/category/{categoryName}� defaults: { _controller: AppBundle:API:searchByCategory }� methods: [GET]

14 of 45

Extend the Rest API : Representation of our Session

Let’s create a class that will represent

our eZ Content

class Session�{� public $title;� public $description;� public $category;� public $protagonist;� public $startDate;� public $endDate;� public $_contentId;� public $_locationId;�� public function __construct(Content $content)� {� $this->title = $content->getFieldValue('title')->text;� $this->description = $content->getFieldValue('description')->text;� $this->category = $content->getFieldValue('category')->text;� $this->protagonist = $content->getFieldValue('protagonist')->text;� $this->startDate = $content->getFieldValue('startDate')->value;� $this->endDate = $content->getFieldValue('endDate')->value;�� $this->_contentId = $content->id;� $this->_locationId = $content->contentInfo->mainLocationId;� }�}

15 of 45

Extend the Rest API : Performs the search

$searchService = $this->container->get('ezpublish.api.repository')->getSearchService();�� $query = new Query(� array(� 'filter' => new LogicalAnd(� array(� new ContentTypeIdentifier( 'session' ),� new Field( 'category', Criterion\Operator::EQ, $categoryName ),� )� )� )� );�� try {� $searchResult= $searchService->findContent($query);� $items = [];�� foreach ($searchResult->searchHits as $item) {� $items[] = new Session($item->valueObject);� }�� return new JsonResponse($items);� } catch (InvalidArgumentException $e) {� throw $e;� }�

curl ezrestapi.websc/api/ezp/v2/search/category/ez

[

{

"title": "Real-life use cases leveraging Solr power",

"description": "",

"category": "ez",

"protagonist": "Petar Španja (Netgen)",

"startDate": {

"date": "2017-08-30 07:30:00.000000",

"timezone_type": 1,

"timezone": "+00:00"

},

"endDate": {

"date": "2017-08-30 10:45:00.000000",

"timezone_type": 1,

"timezone": "+00:00"

},

"_contentId": 578,

"_locationId": 575

},

{

...

}

]

16 of 45

Extend the Rest API : Wait….

17 of 45

Extend the Rest API : let’s do it properly

namespace AppBundle\Rest\values;

class SessionList�{� public $sessions;�� public function __construct( $sessions )� {� $this->sessions = $sessions;� }�}

Create a SessionList class that represent a list our sessions

18 of 45

Extend the Rest API : let’s do it properly

$searchService = $this->container->get('ezpublish.api.repository')->getSearchService();�� $query = new Query(� array(� 'filter' => new LogicalAnd(� array(� new ContentTypeIdentifier( 'session' ),� new Field( 'category', Criterion\Operator::EQ, $categoryName ),� )� )� )� );�� try {� $searchResult= $searchService->findContent($query);� $items = [];�� foreach ($searchResult->searchHits as $item) {� $items[] = new Session($item->valueObject);� }�� return new SessionList($items);� } catch (InvalidArgumentException $e) {� throw $e;� }

Change the response returned by the controller ��We go from JsonResponse to SessionList

19 of 45

Extend the Rest API : Register a visitor

app.rest.value_object_visitor.session_list:� parent: ezpublish_rest.output.value_object_visitor.base� class: AppBundle\Rest\valueObjectVisitor\SessionList� tags:� - { name: ezpublish_rest.output.value_object_visitor, type: AppBundle\Rest\values\SessionList }

First, register your visitor in app/config/services.yml using a specific tag name

Let’s try again our endpoit

20 of 45

Extend the Rest API : Register a visitor

namespace AppBundle\Rest\valueObjectVisitor;���use AppBundle\Rest\values\Session;�use eZ\Publish\Core\REST\Common\Output\ValueObjectVisitor;�use eZ\Publish\Core\REST\Common\Output\Generator;�use eZ\Publish\Core\REST\Common\Output\Visitor;��class SessionList extends ValueObjectVisitor�{� public function visit( Visitor $visitor, Generator $generator, $data )� {� // Your visitor will build the output format of the response� }�}

Second, write the visitor

$generator->startObjectElement('SessionList');� $generator->startList('Sessions');�

/** @var Session $session */� foreach ($data->sessions as $session) {� $generator->startObjectElement('Session');� $generator->startValueElement('title', $session->title);� $generator->endValueElement('title');�� $generator->startValueElement('description', $session->description);� $generator->endValueElement('description');�� ….� $generator->endObjectElement('Session');� }�� $generator->endList('Sessions');� $generator->endObjectElement('SessionList');

In our example

21 of 45

Extend the Rest API : Try again

curl ezrestapi.websc/api/ezp/v2/search/category/ez

<SessionList media-type="application/vnd.ez.api.SessionList+xml">

<Session media-type="application/vnd.ez.api.Session+xml">

<title>Real-life use cases leveraging Solr power</title>

<description></description>

<category>ez</category>

<protagonist>Petar Španja (Netgen)</protagonist>

<startDate>1504078200</startDate>

<endDate>1504089900</endDate>

<_contentId>578</_contentId>

<_locationId>575</_locationId>

</Session>

<Session media-type="application/vnd.ez.api.Session+xml">

<title>EzCoreExtraBundle in practice (Intermediate)</title>

<description>EzCoreExtraBundle provides several additional features to eZ Publish/eZ Platform, such as themes and other devx candies. In this workshop, you will experience different functionnalities that may help you in your projects.</description>

<category>ez</category>

<protagonist>Jérôme Vieilledent (Code Rhapsodie)</protagonist>

<startDate>1504093500</startDate>

<endDate>1504105200</endDate>

<_contentId>579</_contentId>

<_locationId>576</_locationId>

</Session>

Now we should have our media type

22 of 45

Extend the Rest API : JSON with our endpoint?

curl ezrestapi.websc/api/ezp/v2/search/category/ez

-H "Accept: application/vnd.ez.api.SessionList+json”

Now we should have our media type

{

"SessionList": {

"_media-type": "application\/vnd.ez.api.SessionList+json",

"Sessions": [

{

"_media-type": "application\/vnd.ez.api.Session+json",

"title": "Real-life use cases leveraging Solr power",

"description": "",

"category": "ez",

"protagonist": "Petar \u0160panja (Netgen)",

"startDate": "1504078200",

"endDate": "1504089900",

"_contentId": 578,

"_locationId": 575

},

{

"_media-type": "application\/vnd.ez.api.Session+json",

"title": "EzCoreExtraBundle in practice (Intermediate)",

"description": "EzCoreExtraBundle provides several additional features to eZ Publish\/eZ Platform, such as themes and other devx candies. In this workshop, you will experience different functionnalities that may help you in your projects.",

"category": "ez",

"protagonist": "J\u00e9r\u00f4me Vieilledent (Code Rhapsodie)",

"startDate": "1504093500",

"endDate": "1504105200",

"_contentId": 579,

"_locationId": 576

},

23 of 45

eZPlatform’s Rest API

  • Easy to create proper endpoints for the REST API
  • eZPlatform as a headless CMS providing contents
  • Easy to consume the API with these out of the box media types and custom ones
  • Let the front rendering created by front frameworks (VueJS, React, Angular…)

24 of 45

VueJs

25 of 45

What is VueJs

Created by Evan You in 2014

65000 stars on Github

Approachable, Versatile and Performant

26 of 45

Doc is life !

ALL YOU NEED IS DOC !

https://vuejs.org/v2/guide/

27 of 45

Component oriented framework

28 of 45

Component oriented framework

Parent knows datas

Children receive only required datas

Children send events to parent

Parent modify datas

29 of 45

A Vue

<div id="app">

<h1>Hello world !</h1>

</div>

var vue = new Vue({

name: 'app',

el: '#app'

})

30 of 45

Sample page

31 of 45

The page in component

<div>

<app-header></app-header>

<div class="left-col">

<news></news>

<slider></slider>

<news v-for="news in news-list"></news>

</div>

<div class="right-col">

<visits-list></visits-list>

<facebook-widget></facebook-widget>

</div>

<app-footer></app-footer>

</div>

32 of 45

Datas and computed

<div id="app">

<h1>Hello {{ firstName }} {{ lastName }}</h1>

</div>

var vue = new Vue({

name: 'app',

el: '#app',

data: {

firstName: 'Melvin',

lastName: 'Schultz'

},

computed: {

fullName () {

return this.firstName + ' ' + this.lastName

}

})

33 of 45

Conditions

<div id="app">

<h1 v-if="firstVisit">Welcome</h1>

<h1 v-else>Welcome back !</h1>

<h2> Hello {{ fullName }}</h2>

</div>

// ...

data: {

firstName: 'Melvin',

lastName: 'Schultz',

firstVisit: true

},

// ...

34 of 45

Loops

<!-- ... -->

<div v-if="!firstVisit">

<p>Last visits :</p>

<ul>

<li v-for="(date, key) in visits"> {{ date }}</li>

</ul>

</div>

<!-- ... -->

// ...

data: {

firstName: 'Melvin',

lastName: 'Schultz',

firstVisit: false,

visits: ['25/02/2017', '12/03/2017', '04/06/2017']

},

// ...

35 of 45

Directives

v-if, v-else, v-for, v-show, v-on, v-model, v-bind, v-XXXX

<div id="app" v-backgroundImage="’https://vuejs.org/images/logo.png’">

directives: {

backgroundImage: {

update (DOMElement, binding) {

DOMElement.style.background-image = `url("${decodeURI(binding.value)}")`

}

}

},

36 of 45

HTML attributes

<a v-bind:href="docLink">

All you need is doc

</a>

data: {

firstName: 'Melvin',

lastName: 'Schultz',

firstVisit: false,

docLink: 'https://vuejs.org/v2/guide'

},

37 of 45

Forms

<!-- ... -->

<form>

<label>First Name</label>

<input type="text" v-model="firstName">

<label>Last Name</label>

<input type="text" v-model="lastName">

</form>

<!-- ... -->

38 of 45

Events

<!-- ... -->

<form v-on:submit="console.log('submit handled')">

<label>First Name</label>

<input type="text" v-bind="firstName">

<label>Last Name</label>

<input type="text" v-bind="lastName">

</form>

<!-- ... -->

v-on:submit

v-on:click

v-on:focus

v-on:keyup

etc...

39 of 45

Methods

<!-- ... -->

<form v-on:submit="handleFormSubmission">

<label>First Name</label>

<input type="text" v-model="firstName">

<label>Last Name</label>

<input type="text" v-model="lastName">

</form>

<!-- ... -->

// ...

methods: {

handleFormSubmission () {

// do some api call

// with available data

}

}

// ...

40 of 45

The main force of VueJs : his data binding process

  • Datas and props (and also computed) are totally reactives
  • Use of get and set on each value
  • Each element of the virtual DOM using a VueJs value bind a listener, triggered on the set
  • VueJs make a diff between both impacted virtual DOM (before and after the change) and update just a little part of the DOM
  • Optimised global rendering

41 of 45

Some useful modules

42 of 45

Let’s try it !

Duplication of the EZWSC website

43 of 45

Steps

Demo :

  • Display Sessions by category

Do it :

  • Install the VueJs dev environment
  • Display full Session

If we have time :

  • Filter Sessions by speaker

44 of 45

Install the VueJs dev environment

git clone https://bitbucket.org/summercamp17/ezwsc-vuejs.git restapi-vuejs

git checkout step-1-display-sessions

npm install

npm run dev

Go to localhost:8080

45 of 45

Display full session

  • Create a new component SessionFull.vue
  • Add a new route in router/index.js with a parameter for the contentId
  • Add a link on SessionItem pointing on SessionFull
  • Fetch datas from eZ :
    • ezrestapi.websc/api/ezp/v2/content/objects/{contentId}
    • header : {'Accept': 'application/vnd.ez.api.Content+json'}
  • Display it !