#ploneconference2018 - Tokyo
Maik Derstappen
Empower small-scale farmers
gaipa is a global farmer empowerment community equipping farmers to create self-determined and sustainable livelihoods.
with accessible information for farmers
India & Sambia
Android/PC
It has to work also offline
{
"short_name": "MyApp",
"name": "My App, which does something",
"icons": [
{
"src": "/images/icons-512.png", "type": "image/png", "sizes": "512x512"
}
],
"orientation": "portrait",
"start_url": ".",
"background_color": "#9b924c",
"display": "standalone",
"theme_color": "#9b924c"
}
if (workbox) {
workbox.routing.registerRoute(
new RegExp("(https?:\/\/gaipa\.org(\/app\/)()?)"),
workbox.strategies.staleWhileRevalidate()
);
workbox.precaching.precacheAndRoute([]);
}
yarn global add aurelia-cli
au run --watch
au generate component simple-top-bar
This will generate a Aurelia component, consisting of a template and a JavaScript file.
├── app.html
├── app.js
├── environment.js
├── main.js
├── resources
├── simple-top-bar.html
└── simple-top-bar.js
export class SimpleTopBar {
constructor() {
this.message = 'Hello world';
this.friends = ['Chuck Norris', 'John Rambo']
}
}
<template>
<h1>${message}</h1>
</template>
<template>
<ul class="friends">
<li repeat.for="friend of friends">Hello, ${friend}!</li>
</ul>
</template>
If you like Zope Page Templates, you might like Aurelia templates too ;).
import { inject } from "aurelia-framework";
import {ContentApi} from './api';
@inject(ContentApi)
export class SolutionService {
constructor(gaipaContentApi) {
this.contentApi = gaipaContentApi;
}
activate(params) {
this.providerId = params.providerId;
this.serviceId = params.serviceId;
}
bind() {
let path = '/provider/' + this.providerId + '/service/' + this.serviceId;
this.getServiceData(path);
}
getServiceData(path) {
this.contentApi.getService(path)
.then(
service => this.service = service
)
.catch(error => {
this.error = error.message;
});
}
}
<template>
<article>
<h1>${service.title}</h1>
<span>${service.description}</span>
<span
innerhtml.bind="service.text.data | sanitizeHTML"
>${service.text.data}</span>
<a href.bind="service.url"
target="_blank"
title.bind="service.title">open external service website</a>
</article>
</template>
import {HttpClient} from 'aurelia-fetch-client';
import {inject} from 'aurelia-framework';
@inject(HttpClient)
export class BookApi{
constructor(http){
this.http = http;
}
getBooks(){
return this.http.fetch('books.json')
.then(response => response.json())
.then(books => { return books; });
}
}
You can find good documentation online or in books.
By default you have access to all content and almost every other peace of Plone thru the Restful-API.
If you need something in a different way, you can highly customize the Restful-API.
The API automatically converts all field values to JSON compatible data, whenever possible.
@adapter(IChoice, IDexterityContent, Interface)
@implementer(IFieldSerializer)
class ChoiceFieldSerializer(BaseFieldSerializer):
def get_value(self, default=None):
value = getattr(
self.field.interface(self.context),
self.field.__name__,
default,
)
if not value:
return
term = _get_vocab_term(self.context, self.field.vocabularyName, value)
return term
registering the serializer
<configure xmlns="http://namespaces.zope.org/zope">
<adapter factory=".choice_field.ChoiceFieldSerializer" />
</configure>
With services, you can get non-content data, as the Navigation or Breadcrumb info's.
GET /plone/front-page
{
"@components": {
"breadcrumbs": {
"@id": "http://localhost:55001/plone/front-page/@breadcrumbs"
}
},
"@id": "http://localhost:55001/plone/front-page",
"@type": "Document",
"UID": "SomeUUID000000000000000000000001",
"allow_discussion": false,
"changeNote": "",
"contributors": [],
"created": "2016-01-21T01:14:48+00:00",
"creators": [
"test_user_1_"
],
"description": "Congratulations! You have successfully installed Plone.",
"effective": null,
"exclude_from_nav": false,
"expires": null,
"id": "front-page",
"is_folderish": false,
"language": "",
"layout": "document_view",
"modified": "2016-01-21T01:24:11+00:00",
"parent": {
"@id": "http://localhost:55001/plone",
"@type": "Plone Site",
"description": "",
"title": "Plone site"
},
"relatedItems": [],
"review_state": "private",
"rights": "",
"subjects": [],
"table_of_contents": null,
"text": {
"content-type": "text/plain",
"data": "<p>If you're seeing this instead of the web site you were expecting, the owner of this web site has just installed Plone. Do not contact the Plone Team or the Plone mailing lists about this.</p>",
"encoding": "utf-8"
},
"title": "Welcome to Plone",
"version": "current",
"versioning_enabled": true
}
GET /plone/front-page/@breadcrumbs
{
"@id": "http://localhost:55001/plone/front-page/@breadcrumbs",
"items": [
{
"@id": "http://localhost:55001/plone/front-page",
"title": "Welcome to Plone"
}
]
}
GET /plone/front-page?expand=breadcrumbs
{
"@id": "http://localhost:55001/plone/front-page",
"@type": "Document",
"@components": {
"actions": {
"@id": "http://localhost:55001/plone/front-page/@actions"
},
"breadcrumbs": {
"@id": "http://localhost:55001/plone/front-page/@components/breadcrumbs",
"items": [
{
"title": "Welcome to Plone",
"url": "http://localhost:55001/plone/front-page"
}
]
},
"navigation": {
"@id": "http://localhost:55001/plone/front-page/@navigation"
},
"schema": {
"@id": "http://localhost:55001/plone/front-page/@schema"
},
"workflow": {
"@id": http://localhost:55001/plone/front-page/@workflow"
},
},
"UID": "1f699ffa110e45afb1ba502f75f7ec33",
"title": "Welcome to Plone"
}
create the following structure in our Plone package
services/
├── configure.zcml
├── __init__.py
└── related_articles
├── configure.zcml
├── get.py
├── __init__.py
<configure xmlns="http://namespaces.zope.org/zope">
<include package=".related_articles" />
</configure>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:plone="http://namespaces.plone.org/plone">
<adapter factory=".get.RelatedArticles" name="related-articles"/>
<plone:service
method="GET"
for="zope.interface.Interface"
factory=".get.RelatedArticlesGet"
name="@related-articles"
permission="zope2.View"
/>
</configure>
@implementer(IExpandableElement)
@adapter(Interface, Interface)
class RelatedArticles(object):
def __init__(self, context, request):
self.context = context
self.request = request
def __call__(self, expand=False):
result = {
'related-articles': {
'@id': '{}/@related-articles'.format(
self.context.absolute_url(),
),
},
}
if not expand:
return result
solution_categories = [c for c in self.context.solution_category]
article_brains = api.content.find(
portal_type="Chapter",
solution_category={
'query': solution_categories,
'operator': 'and',
}
)
items = []
for article in article_brains:
items.append({'title': article.Title, '@id': article.getURL()})
result['related-articles']['items'] = items
return result
class RelatedArticlesGet(Service):
def reply(self):
related_articles = RelatedArticles(self.context, self.request)
return related_articles(expand=True)['related-articles']
GET /plone/some-content?expand=related-articles
"@components": {
"breadcrumbs": {
"@id": "http://localhost:7080/plone/some-content/@breadcrumbs"
},
"navigation": {
"@id": "http://localhost:7080/plone/some-content/@navigation"
},
"related-articles": {
"@id": "http://localhost:7080/plone/some-content/@related-articles",
"items": [{
"@id": "http://localhost:7080/plone/articles/harvesting",
"title": "Harvesting"
}]
}
},
Maik Derstappen - Derico - md@derico.de
Slides online: http://derico.de/talks