Skip to content

Website Entwicklung mit Scrivito, JavaScript und ReactJS

Bartek Ochab edited this page Feb 27, 2018 · 6 revisions

Konzeptionelle Grundlagen

Cloud Service

  • Dienst der in der Could (Amazon, Google, Microsoft) läuft
  • Scrivito wird von uns bei AWS betrieben (eu-west-1, Irland)

Headless CMS

  • ausschließlich Daten verwalten
  • macht keine Annahme oder Hilfestellung über Ihre Darstellung
  • Scrivito selbst verwaltet nur Daten, jedoch keine Templates

Cloud CMS

  • Inhalte werden in der Cloud gespeichert
  • Zugriff nur über API

Serverless CMS

  • keinen eigenen(!) Server als Infrastruktur betreiben
  • keinen dedizierten Server - nur API Backend

JavaScript

  • Läuft in allen Browser
    • Erlaubt dynamisches Verhalten
    • Erlaubt Verhalten fast wie eine Desktop Applikation
  • node.js
    • Server Applikation die JavaScript laufen lässt
    • Kann als Webserver fungieren ( Express )
      • Erlaubt hohen Grad an “Parallelisierung” aufgrund nicht blockierender Architektur
      • Dennoch nur 1 echter Prozess
    • Kann zum laufen von serverseitigen Tasks genutzt werden
      • Image Processing, css Processing
    • Kann für lokale Applikationen genutzt werden ( Electron )
      • Zb Atom Code-Editor
    • Browser Code kann lokal getestet werden ( Mocha Jasmine )
  • Google berücksichtigt JavaScript Seiten auch bei der Indizierung

React

  • JavaScript Framework
  • Fokus auf Interface Entwicklung
  • Deklarative Programmierung
    • Gegeben einen Zustand beschreibe was angezeigt wird
    • Zustandsübergänge werden meist von React automatisch gehandhabt
  • Komponenten basiert
    • Jeder HTML-Tag kann eine Komponente sein
    • Komponenten sind hierarchisch angeordnet
    • Komponenten aktualisieren sich unabhängig von einander

Scrivito JS Konzepte

  • Webseite (Live)
    • API erlaubt Zugriff auf anzuzeigende Daten
    • Browser rendert für jeden Besucher seine(!) Version der Seite
    • Wir liefern nur “Verhalten” in Form von JavaScript-Code
  • Bearbeitung
    • Inhalte direkt in der Seite bearbeiten (WYSIWYG)
    • Die Webseite ist das CMS-Interface
  • Working Copies
    • Ähnlich zu Git Branches
    • Konzeptionell eine Kopie der aktuell publizierten Webseite
    • Erlaubt paralleles Arbeiten

Scrivito JS Programming

Neue Applikation anlegen

Infopark & Friends Workshop-App

Basic Scrivito JS Applikation for the Infopark & Friends Workshop: Website-Entwicklung mit Scrivito, JavaScript und ReactJS

Tennant Setup

  • Anmelden bei [scrivito.com]
  • neues Trial CMS Anlegen
  • App auschecken git clone git@github.com:infopark/ip-friends.git
  • API-Key holen / .env gemäß Hinweisen erstellen
  • cd ip-friends
  • npm install
  • npm start
  • Im Borwser zu to /scrivito gehen
  • neue Working Copy anlegen
  • In edit Modus wechseln
  • Browser Console öffnen
  • Frage auf "scrivito-application" wechseln
  • In der Konsole folgende Kommandos ausführen
Scrivito.load(() => [...Scrivito.Obj.all()])
  .then((objs) => {
    objs.forEach(obj => {
      console.log("Obj: " + obj.id());
      obj.destroy();
    });
    Scrivito.getClass('Homepage').create({
      _path: "/"
    });
    console.log("Done");
  });

Beispiel: Einen Blog verfassen

  • Wir gehen nicht auf alle Details ein
  • Ziel ist es ein grobes Verständnis zu geben
  • Beispiele zeigen Optionen nicht optimale Lösungen

Blog Einträge

  • Einzelne Blog-Seiten mit einem dedizierten Artikel
  • Seiten als auch Widgets bestehen aus 3 Komponenten
    • Model
      • Enthält welche Daten gespeichert werden sollen
      • eventuell Verhalten
    • Ansichten/Komponenten
      • Eigentliche Darstellung der Seite
    • Bearbeitung konfigurieren
      • Detailsansicht konfigurieren

Modelle erzeugen

Objs/BlogPost/BlogPostObjClass.js

Scrivito.provideObjClass('BlogPost', {
  attributes: {
    author: 'string',
    body: 'widgetlist',
    pageTitle: 'string',
    publishedAt: 'date',
    title: 'string',
    relatedLink: 'linklist',
  },
});
  • Das wäre ein Obj-Klasse nur mit Attributen
  • Attribut typen
    • binary
    • date
    • enum
    • multienum
    • html
    • link / linklist
    • reference / referencelist
    • string
    • stringlist - ergo tags
    • integer
    • float
    • widgetlist
  • Modelle können
    • tatsächliche Seiten sein
    • abstrakte Container für wiederverwendbare Daten / Ressourcen Objekte

Komponenten erzeugen

  • Jede Objekt klasse hat eigene Komponenten

src/Objs/BlogPost/BlogPostComponent.js

Scrivito.provideComponent('BlogPost', ({ page }) => {
  const relatedLinks = page.get('relatedLink').map(link => {
    return (<li key={ link.hash() }>
      <Scrivito.LinkTag to={ link }>{ link.title() }</Scrivito.LinkTag>
    </li>);
  });

  return (
  <div className="content blog_post">
    <Scrivito.ContentTag tag="h2" content={ page } attribute="title" />
    <div>
      By <Scrivito.ContentTag tag="span" content={ page } attribute="author" />
    </div>
    <div>
      <Scrivito.ContentTag tag="span" content={ page } attribute="publishedAt" />
    </div>
    <hr/>
    <Scrivito.ContentTag tag="div" content={ page } attribute="body" className="panel-body" />
    <ul>
      { relatedLinks }
    </ul>
  </div>
  );
});

Konfiguration Detailsansicht

src/Objs/BlogPost/BlogPostEditingConfig.js

import HeadlineWidget from '../../Widgets/HeadlineWidget/HeadlineWidgetClass';
import TextWidget from '../../Widgets/TextWidget/TextWidgetClass';

Scrivito.provideEditingConfig('BlogPost', {
  title: 'BlogPost',
  description: 'Ein Blog Artikel',
  attributes: {
    title: {
      title: 'Titel des Artikels',
    },
    pageTitle: {
      title: 'Seitentitel',
    },
    author: {
      title: 'Author des Artikels',
    },
    publishedAt: {
      title: 'Datum der Veröffentlichung',
    },
    relatedLink: {
      title: 'Verwandte Links',
    },
  },
  properties: [
    'title',
    'pageTitle',
    'author',
    'publishedAt',
    'relatedLink',
  ],
  initialContent: {
    pageTitle: '[Neuer Blog Post]',
    body: [
      new HeadlineWidget({ headline: 'Ein neuer Blog' }),
      new TextWidget({ text: 'Hier Artikel einfügen' }),
    ],
    publishedAt: new Date(Date.now()),
  },
  titleForContent: obj => obj.get('title'),
});

Ein einfaches Widget schreiben

  • Teaser Widget das Bild + Text kombiniert

Model für neues Widget anlegen

  • ähnlich zu Seiten

src/Widgets/TeaserWidget/TeaserWidgetClass.js

Scrivito.provideWidgetClass('TeaserWidget', {
  attributes: {
    title: 'string',
    summery: 'html',
    headerImage: 'reference',
  },
});

Komponente für das Widget anlegen

src/Widgets/TeaserWidget/TeaserWidgetComponent.js

Scrivito.provideComponent('TeaserWidget', ({ widget }) => {
  return (
    <div>
      <Scrivito.ImageTag content={ widget } attribute="headerImage" />
      <Scrivito.ContentTag tag="h2" content={ widget } attribute="title" />
      <Scrivito.ContentTag tag="div" content={ widget } attribute="summery" />
    </div>
  );
});

Auto Transform für Bilder

  • Scrivoto.ImageTag skaliert Bilder automatisch
    • Verhindert das diese nicht zu groß werde.
  • Bilder können auch manuell in einer bestimmten Größe angefragt werden
image.get('blob').optimizeFor({ width: 50, height: 50, fit: 'crop' }).url()

Konfiguration Detailsansicht

src/Widgets/TeaserWidget/TeaserWidgetEditingConfig.js

Scrivito.provideEditingConfig('TeaserWidget', {
  title: 'Teaser Widget',
  attributes: {
    headerImage: {
      title: 'Titelbild',
    },
  },
  properties: [
    'headerImage',
  ],
  titleForContent: widget => widget.get('title'),
});

Widgets in Blog Posts einschränken

Objs/BlogPost/BlogPostObjClass.js

Scrivito.provideObjClass('BlogPost', {
  attributes: {
    author: 'string',
    body: ['widgetlist', { only: ['HeadlineWidget', 'TextWidget', 'TeaserWidget'] }],
    pageTitle: 'string',
    publishedAt: 'date',
    title: 'string',
    relatedLink: 'linklist',
  },
});

Übersichtsseite

  • Blog Seite mit Artikelübersicht ( zb wie eine Newsseite )
  • Attribute alleine reichen nicht, wir brauchen noch Verhalten

Modelle erzeugen

  • Wir brauchen aber noch Verhalten
    • Liste letzten BlogPosts

Objs/Blog/BlogObjClass.js

const BlogBaseClass = Scrivito.createObjClass({
  attributes: {
    pageTitle: 'string',
  },
});

class Blog extends BlogBaseClass {
  static latestPosts(page = 0, count = 10) {
    return Scrivito.Obj
      .where('_objClass', 'equals', 'BlogPost')
      .offset(page * count)
      .order('publishedAt', 'desc')
      .take(count);
  }
}

export default Scrivito.provideObjClass('Blog', Blog);
  • Objekte können bequem gesucht werden
    • Scrivito.Obj.where('_objClass', 'equals', ['Image', 'Download'])
    • Scrivito.Obj.where('_objClass', 'equals', 'BlogPost').and('_permalink', 'equals', null)
    • Scrivito.Obj.where('_objClass', 'equals', 'BlogPost').and('title', 'contains', "brand")
    • Scrivito.Obj.where('_objClass', 'equals', 'BlogPost').order('_last_changed','desc').take(1)[0]
    • Scrivito.Obj.where('*', 'links_to', i)
    • Scrivito.Obj.where('_objClass', 'equals', 'BlogPost').and('_obj_class', 'equals', 'GoogleMapsWidget')
      • beschränke Suche auf Objekte der Classe BlogPost (und alle enthaltenten Widgets)
      • Suche Widgets einer bestimmten Klasse
      • Gibt aber dennoch BlogPost Objekte aus

Komponenten erzeugen

src/Objs/Blog/BlogComponent.js

import Blog from './BlogObjClass';

Scrivito.provideComponent('Blog', ({ page }) => {
  const blogPosts = () => {
    const content = [];
    const posts = Blog.latestPosts();
    if (!posts) { return; }
    for (let i = 0, l = posts.length; i < l; i += 1) {
      const post = posts[i];
      content.push(
        <div key={ post.id() }>
          <h3>
            <Scrivito.LinkTag to={ post }>{ post.get('title') }</Scrivito.LinkTag> <small>
              { post.get('publishedAt').toDateString() }
              <br/>
              by { post.get('author')}
            </small>
          </h3>
        </div>
      );
    }
    return content;
  };

  return (
  <div className='content blog'>
    <Scrivito.ContentTag tag='h1' content={ page } attribute='pageTitle' />
    <div className='blog_posts'>
      { blogPosts() }
    </div>
  </div>
  );
});

Konfiguration Detailsansicht

src/Objs/Blog/BlogEditingConfig.js

Scrivito.provideEditingConfig('Blog', {
  title: 'Blog',
  description: 'Ein Blog Artikel',
  attributes: {
    pageTitle: {
      title: 'Seitentitel',
    },
  },
  properties: [
    'pageTitle',
  ],
  titleForContent: obj => obj.get('title'),
});

Bild Model erweitern

  • Bilder in lizenziert und frei unterscheiden

Image Modell erweitern

src/Objs/Image/ImageObjClass.js

export default Scrivito.provideObjClass('Image', {
  attributes: {
    blob: 'binary',
    tags: 'stringlist',
    alternativeText: 'string',
    licence: ['enum', { values: ['free', 'cc', 'payed'] }],
  },
});

Image Bild-Details erweitern

src/Objs/TimelineEntry/ImageEditingConfig.js

Scrivito.provideEditingConfig('Image', {
  attributes: {
    blob: {
      title: 'Image',
    },
    tags: {
      title: 'Tags',
      description: 'Make it easier to find this Image by adding some tags.',
    },
    licence: {
      title: 'Bildlizenz',
      values: [
        { value: 'free', title: 'Frei' },
        { value: 'cc', title: 'Creative Commons' },
        { value: 'payed', title: 'Bezahlt' },
      ],
    },
    alternativeText: {
      title: 'Alternative text',
      description: 'Brief description of what the image is about.',
    },
  },
  properties: [
    'blob',
    'alternativeText',
    'tags',
    'licence',
  ],
});

Content Browser

  • Fenster zum gesamten Content im CMS für den Redakteur
  • Konfiguration definiert hierarchische Filter

Content Browser Filter für bezahlte Bilder

src/config/scrivitoContentBrowser.js

Scrivito.configureContentBrowser({
  filters: {
    _objClass: {
      field: '_objClass',
      operator: 'equals',
      options: {
        ...
        Images: {
          title: 'Images',
          icon: 'image',
          value: 'Image',
          options: {
            free: {
              field: 'licence',
              operator: 'equals',
              value: 'free',
              title: 'Frei',
            },
            cc: {
              field: 'licence',
              operator: 'equals',
              value: 'cc',
              title: 'Creative Commons',
            },
            payed: {
              field: 'licence',
              operator: 'equals',
              value: 'payed',
              title: 'Gekauft',
            },
          },
        }
      }
    }
  }
});

Autor Ressourcen Objekt definieren

  • Ist abstrakte Ressource die keine Seite im Auftritt ist.
  • Details Ansicht für Content Browser zum Bearbeiten:
  • Content Browser Filter definieren
  • Blog Post umstellen von string Autor auf Ressourcen Objekt

Modelle erzeugen

Objs/Author/AuthorObjClass.js

export default Scrivito.provideObjClass('Author', {
  attributes: {
    firstName: 'string',
    lastName: 'string',
    image: 'reference',
  },
});
  • BlogPost abändern, um Autoren Referenz zu nutzen

Objs/BlogPost/BlogPostObjClass.js

Scrivito.provideObjClass('BlogPost', {
  attributes: {
    author: 'reference',
    body: ['widgetlist', { only: ['HeadlineWidget', 'TextWidget', 'TeaserWidget'] }],
    pageTitle: 'string',
    publishedAt: 'date',
    title: 'string',
    relatedLink: 'linklist',
  },
});

Komponenten erzeugen

src/Objs/Author/AuthorComponent.js

class AuthorComponent extends React.Component {
  render() {
    let fullName = '';
    if (this.props.page) {
      fullName = `${this.props.page.get('firstName')} ${this.props.page.get('lastName')}`;
    }
    return <span>
      { fullName }
    </span>;
  }
}

export default Scrivito.connect(AuthorComponent);
Scrivito.provideComponent('Author', AuthorComponent);
  • BlogPost abändern, um den referenzierten Autor anzuzeigen

src/Objs/BlogPost/BlogPostComponent.js

import Author from '../Author/AuthorComponent';

Scrivito.provideComponent('BlogPost', ({ page }) => {
  const relatedLinks = page.get('relatedLink').map(link => {
    return (<li key={ link.hash() }>
      <Scrivito.LinkTag to={ link }>{ link.title() }</Scrivito.LinkTag>
    </li>);
  });

  return (
  <div className="content blog_post">
    <Scrivito.ContentTag tag="h2" content={ page } attribute="title" />
    <div>
      By <Author page={ page.get('author') }/>
    </div>
    <div>
      <Scrivito.ContentTag tag="span" content={ page } attribute="publishedAt" />
    </div>
    <hr/>
    <Scrivito.ContentTag tag="div" content={ page } attribute="body" className="panel-body" />
    <ul>
      { relatedLinks }
    </ul>
  </div>
  );
});

src/Objs/Blog/BlogComponent.js

import Blog from './BlogObjClass';
import Author from '../Author/AuthorComponent';

Scrivito.provideComponent('Blog', ({ page }) => {
  const blogPosts = () => {
    const content = [];
    const posts = Blog.latestPosts();
    if (!posts) { return; }
    for (let i = 0, l = posts.length; i < l; i += 1) {
      const post = posts[i];
      content.push(
        <div key={ post.id() }>
          <h3>
            <Scrivito.LinkTag to={ post }>{ post.get('title') }</Scrivito.LinkTag> <small>
              { post.get('publishedAt').toDateString() }
              <br/>
              by <Author page={ post.get('author') }/>
            </small>
          </h3>
        </div>
      );
    }
    return content;
  };

  return (
  <div className='content blog'>
    <Scrivito.ContentTag tag='h1' content={ page } attribute='pageTitle' />
    <div className='blog_posts'>
      { blogPosts() }
    </div>
  </div>
  );
});

Konfiguration Detailsansicht

src/Objs/Author/AuthorEditingConfig.js

Scrivito.provideEditingConfig('Author', {
  title: 'Author',
  description: 'Der Author eines Artikels',
  attributes: {
    firstName: {
      title: 'Vorname des Autors',
    },
    lastName: {
      title: 'Nachname des Autors',
    },
    image: {
      title: 'Avatar Bild',
    },
  },
  properties: [
    'firstName',
    'lastName',
    'image',
  ],
  titleForContent: obj => obj.get('title'),
});

Content Browser erweitern

src/config/scrivitoContentBrowser.js

Scrivito.configureContentBrowser({
  filters: {
    _objClass: {
      field: '_objClass',
      operator: 'equals',
      options: {
        ...
        Authors: {
          title: 'Autoren',
          icon: 'person',
          value: 'Author',
        },
      }
    }
  }
});

Routing

Geht zur konfigurierten Homepage i.e. /

Scrivito.configure({
  homepage: () => {
    return Scrivito.Obj.where('_objClass', 'equals', 'Homepage').take(1)[0];
  },
  ...
});

Id Pfad /slug-id

  • slug kann individuell festgelegt werden

Objs/Blog/BlogObjClass.js

...
class Blog extends BlogBaseClass {
  ...
  slug() {
    return this.get('pageTitle').replace(/ /g, '-').replace(/[^\w-]+/g, '');
  }
  ...
}
...

Permalink

  • Werden am Obj festgelegt
  • Erlauben Adressierung über eindeutigen String

#Q&A

  • Wie wird die Verbindung vom Browser zur Cloud gesichert
    • API-Key + Domain whitelist
    • identisch wie GoogleMaps seine Zugriffe Schützt
  • Wie erfolgt Authentifizierung/Editor Sicherung?
    • Single sign on mit scrivito.com (cookie)
  • Wie binde ich alternative Datenbanken an?
    • Eigenes Backend
      • Erfordert einen API-Key (Credentials)
      • Erlaubt nur Anfragen von einer bestimmten Domain

JavaScript Vorkenntnisse

  • node >= 8.0.0
  • npm >= 5.0.0 (already part of nodejs 8.x).
  • Verständnis von ReactJs sind vorteilhaft