Search by

tietge / silverstripe-element-toolbar

moritz-sauer-13

Werkzeugleiste für Elemental-Blöcke im Frontend: Sprung ins CMS und Stufenfelder (z. B. Abstände) direkt am Block stellen – mit sofortiger Vorschau und Speichern im Hintergrund.

Package info

git.innomedia.de/Tietge/silverstripe-element-toolbar

Type:silverstripe-vendormodule

pkg:composer/tietge/silverstripe-element-toolbar

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

1.0.1 2026-10-07 13:42 UTC

This package is auto-updated.

Last update: 2026-10-07 13:43:17 UTC


README

Eine Werkzeugleiste für Elemental-Blöcke im Frontend (Silverstripe CMS 6). Angemeldete Redakteure sehen beim Überfahren eines Blocks eine Marke mit Blocktyp, Titel und dem Sprung in den Block im CMS – und je konfigurierter Steuerung einen Stepper an der Kante des Blocks, mit dem sich ein Stufenfeld (typisch: der Abstand oben/unten) direkt am Block stellen lässt. Die Änderung ist sofort zu sehen und wird im Hintergrund gespeichert; ein getöntes Band zeigt beim Überfahren die Fläche, die gerade verstellt wird.

Dazu ein Dock unten links, das beim Scrollen mitläuft: ein Schalter blendet alle Werkzeuge aus (für die Vorführung beim Kunden, ohne sich abzumelden) und verlinkt die Seite im CMS.

Besucher ohne Anmeldung bekommen nichts davon: kein Markup, kein Skript, kein Stylesheet.

Installation

composer require tietge/silverstripe-element-toolbar
vendor/bin/sake flush
composer vendor-expose

Wird das Modul in einem Projekt als Path-Repo mit symlink: false eingebunden (etwa unter packages/), leitet Composer die Version aus dem Git-Stand ab. Unkommittierte Änderungen am Paket kopiert composer update deshalb nicht nach vendor/ – dafür composer reinstall tietge/silverstripe-element-toolbar und danach flush.

Es wird keine Datenbankspalte ergänzt. Das Modul hängt ToolbarExtension an BaseElement und registriert die Route _element-toolbar/set (nur POST, CSRF-Token, canEdit() je Block). Die Leiste trägt die Adresse absolut in data-endpoint – ein <base>-Tag ist nicht nötig.

Einbau im Projekt (drei Stellen)

1. Der ElementHolder. Im überschriebenen templates/DNADesign/Elemental/Layout/ElementHolder.ss die Leiste als letztes Kind des Holders einfügen:

<div class="element $SimpleClassName.LowerCase …" id="$Anchor">
    $Element
    <% if $ElementToolbarActive && $Element.canEdit %>
        <% include Tietge\ElementToolbar\Toolbar %>
    <% end_if %>
</div>

Der Holder liegt außerhalb jedes <% cached %>-Blocks – nur deshalb darf das Markup vom Anmeldezustand abhängen. Die Leiste gehört nie in ein Element-Template.

2. Das Dock in Page.ss, ebenfalls außerhalb jedes Cache-Blocks, z. B. vor </body>:

<% if $ElementToolbarActive %><% include Tietge\ElementToolbar\Dock %><% end_if %>

3. Die Steuerungen. Das Modul weiß nicht, welche Felder ein Block hat. Eine Extension am Block (oder der Block selbst über provideToolbarControls(): array) liefert Control-Objekte:

use Tietge\ElementToolbar\Control;

public function updateToolbarControls(array &$controls): void
{
    $controls[] = new Control(
        field: 'SpacingTop',
        label: 'Abstand oben',
        short: 'Oben',
        position: Control::TOP,
        options: [
            'none' => ['title' => 'Kein Abstand', 'class' => 'space-top-none'],
            'sm'   => ['title' => 'Klein',        'class' => 'space-top-sm'],
            'md'   => ['title' => 'Mittel',       'class' => 'space-top-md'],
        ],
    );
}
  • options ist geordnet – der Stepper geht die Reihenfolge rauf und runter.
  • class ist die Klasse, die der Holder für diesen Wert trägt. Das Skript tauscht sie sofort, der Server bestätigt nach dem Speichern mit getToolbarClasses(). Wer die Klasse im Holder ausgibt, ist Sache des Projekts (z. B. $SpacingClass).
  • Je Kante (top, bottom) eine Steuerung; zwei an derselben Kante lägen übereinander.
  • Die Felder sollten Enums sein (mit Fluent nicht lokalisiert).

Der Titel in der Marke ist $Title; wer Auszeichnungen entfernen will (TextFormatter o. ä.), hängt updateToolbarTitle(string &$title) an dieselbe Extension.

Marke auf halber Höhe

Liegt über der oberen Kante eines Blocks etwas Fremdes – ein absolut gesetzter Header über dem Hero –, ist die Marke rechts oben verdeckt und nicht anklickbar. Solche Blöcke setzen sie an den rechten Rand auf halbe Höhe:

HeroElement:
  toolbar_badge_position: middle   # Vorgabe: corner

Im Einzelfall entscheidet der Hook updateToolbarBadgePosition(string &$position).

Entwurf oder Live

Redakteure sehen im Frontend die Live-Fassung. Deshalb gilt (publish_when_clean: true): Hatte der Block keine unveröffentlichten Änderungen, wird er nach dem Schreiben veröffentlicht – der Wert ist live, so wie er auf dem Bildschirm steht. Hatte er welche, bleibt es beim Entwurf und die Marke sagt „Nur im Entwurf gespeichert". Alles andere hieße, fremde, halb fertige Änderungen mit zu veröffentlichen. false schreibt immer nur den Entwurf.

Mit tractorcow/silverstripe-fluent schreibt der Endpunkt im Zustand der Standardsprache.

Konfiguration

Tietge\ElementToolbar\State:
  permission_code: 'CMS_ACCESS'     # wer die Werkzeuge sieht (je Block zusätzlich canEdit)
  storage_key: 'element-toolbar'    # localStorage-Schlüssel des Schalters „Bearbeitungshilfen"

Tietge\ElementToolbar\ToolbarController:
  publish_when_clean: true

Aussehen

Das CSS (client/css/element-toolbar.css) ist absichtlich ungelayert und ohne Framework: Werkzeuge müssen gegen jedes Projekt-CSS gewinnen. Farben sind bewusst die des CMS (Blau #0071c4, Grün #008a00) und nicht die des Auftritts – alles Blaue ist Werkzeug. Anpassbar über Custom Properties auf :root des Projekts (das Modul deklariert dort nichts, ein Projektwert gewinnt immer):

:root {
    --element-toolbar-color: #0071c4;
    --element-toolbar-on: #008a00;
    --element-toolbar-text: #fff;
    --element-toolbar-inset: 10px;          /* Abstand der Marken zur Blockkante */
    --element-toolbar-ease: cubic-bezier(0.22, 1, 0.36, 1);
    --element-toolbar-duration: 500ms;      /* Polster- und Bandhöhe */
    --element-toolbar-z: 40;
    --element-toolbar-font: system-ui, sans-serif;
    --element-toolbar-radius: 10px;         /* Feld des Docks */
    --element-toolbar-dock-bottom: 10px;    /* Abstand des Docks zum unteren Rand; Fallback: inset.
                                               Für Projekte mit einer mobilen Leiste am unteren Rand */
}

Wird ein Block schmal (Telefon, halbbreiter Block), teilen sich Marke und oberer Stepper die Kante: Unter 52rem Blockbreite fällt der Titel aus der Marke, unter 40rem rückt der Stepper an die linke Kante – Container-Queries am Block, nicht am Fenster.

Zustände hängen an Attributen: .element:hover blendet die Leiste ein, data-open am Dock öffnet das Feld, aria-checked am Schalter, data-element-toolbar="hidden" am <html> blendet alles aus. Das Dock ist auf allen Breiten da; wer unten eine mobile Leiste hat, hebt es mit --element-toolbar-dock-bottom darüber.

Was das Modul voraussetzt

  • Der Holder trägt die Klasse element (Elemental-Standard) und ist das Element, an dem die Klassen der Steuerungen stehen. Er bekommt position: relative, sobald er eine Leiste enthält.
  • Icons (Hugeicons, MIT) liegen inline in den Templates – kein Icon-System nötig.

Bekannte Eigenheit von Silverstripe

Requirements setzt Skripte vor das erste <script> im Body, nicht ans Ende – sobald eine Seite ein Inline-Script mitbringt (Karten, Einbettungen), läuft element-toolbar.js mitten im Dokument. Das Skript ist deshalb so gebaut, dass es nichts beim Laden festhält: Dock und Leisten werden bei jedem Ereignis gesucht, der gemerkte Schalterzustand wird nach DOMContentLoaded hergestellt. Ein Projekt braucht dafür kein Requirements::set_force_js_to_bottom(true).

Lizenz

BSD-3-Clause.