MBlock erzeugt aus einem Modul-Formular beliebig viele sortierbare Datenblöcke und speichert sie als Array in einem REX_VALUE. Diese Seite beschreibt die PHP-API, die JavaScript-Schnittstelle, die Templates und den Build.
- REDAXO ^5.18
- bloecks (liefert Sortable.js für Drag & Drop; ohne bloecks lädt MBlock Sortable.js selbst nach)
- MForm (optional, für MForm-Formulare)
Alle Klassen liegen im Namespace FriendsOfRedaxo\MBlock. Die alten globalen Namen (MBlock, MBlockValueHandler, MBlockSystemButtonReplacer, mblock_rex_form, ...) bleiben als Aliase erhalten, bestehender Code läuft also ohne use-Statement weiter.
use FriendsOfRedaxo\MBlock\MBlock;| Klasse | Aufgabe |
|---|---|
MBlock |
show() und die Daten-/Frontend-Helfer |
Handler\MBlockValueHandler |
Lädt die gespeicherten Werte (REX_VALUE, rex_form, YForm) |
Parser\MBlockParser |
Rendert Wrapper- und Element-Templates |
Provider\TemplateProvider, Provider\MBlockTemplateFileProvider |
Laden Templates aus data/templates/ |
Replacer\* |
Schreiben Namen, Ids und Werte in das Formular-HTML |
Processor\mblock_rex_form |
rex_form-Variante mit MBlock-Feldern |
Utils\MBlockJsonHelper, Utils\MBlockSettingsHelper, Utils\MBlockSessionHelper, Utils\MBlockPageHelper |
Hilfsklassen |
MBlock::show($id, $form, array $settings = []): string| Parameter | Beschreibung |
|---|---|
$id |
Nummer des REX_VALUE (1–20). Für YForm: 'yform::tabelle::feld'. |
$form |
Formular-HTML als String, ein MForm-Objekt, ein mblock_rex_form oder ein rex_yform. |
$settings |
Optionen, siehe unten. |
Jeder Feldname im Formular muss dem Muster REX_INPUT_VALUE[$id][0][feld] folgen (MForm: "$id.0.feld"). MBlock ersetzt den Index 0 je Block. Native Widgets (REX_MEDIA, REX_MEDIALIST, REX_LINK, REX_LINKLIST) werden erkannt und je Block mit eindeutigen Ids versehen. Wichtig: Die Widget-Ids (1–10) müssen über alle MBlocks eines Moduls eindeutig sein.
Optionen:
| Option | Standard | Beschreibung |
|---|---|---|
min |
– | Mindestanzahl Blöcke; so viele werden initial angezeigt, Löschen-Buttons sind darunter gesperrt |
max |
– | Maximale Anzahl; der Hinzufügen-Button wird darüber gesperrt |
copy_paste |
Einstellung "Copy & Paste" | Kopieren/Einfügen-Buttons anzeigen |
delete_confirm |
Einstellung "Bestätigung vor dem Löschen" | 1 = Standardtext, 0 = aus, String = eigener Text |
input_delete |
Einstellung "Eingaben leeren" | Werte in neu hinzugefügten Blöcken leeren |
smooth_scroll |
Einstellung "Smooth Scroll" | Zum neuen Block scrollen |
Alle Optionen landen als data-*-Attribute am Wrapper (data-min, data-max, data-copy_paste, ...).
Der Online/Offline-Schalter hat keine eigene Option: Er erscheint, sobald das Formular ein Hidden-Feld mblock_offline enthält und die Einstellung "Online/Offline" aktiv ist.
$mform->addHiddenField("$id.0.mblock_offline", '0');Das aktive Template wird global in den Einstellungen gewählt (mblock_theme), nicht pro Aufruf.
use FriendsOfRedaxo\MForm;
use FriendsOfRedaxo\MBlock\MBlock;
$id = 1;
$mform = MForm::factory()
->addFieldsetArea('Teammitglied', MForm::factory()
->addTextField("$id.0.name", ['label' => 'Name'])
->addMediaField(1, ['label' => 'Foto'])
->addHiddenField("$id.0.mblock_offline", '0')
);
echo MBlock::show($id, $mform, ['min' => 1, 'max' => 10]);Die Blöcke liegen als Array im REX_VALUE. rex_var::toArray("REX_VALUE[1]") liefert alle Blöcke; die MBlock-Methoden filtern zusätzlich nach dem Online/Offline-Feld.
MBlock::getDataArray(string $rexValue, string $filter = 'all', string $offlineField = 'mblock_offline'): array
MBlock::getOnlineDataArray(string $rexValue, string $offlineField = 'mblock_offline'): array
MBlock::getOfflineDataArray(string $rexValue, string $offlineField = 'mblock_offline'): array
MBlock::filterByStatus(array $data, string $filter = 'all', string $offlineField = 'mblock_offline'): array
MBlock::getOnlineItems(array $data, string $offlineField = 'mblock_offline'): array
MBlock::getOfflineItems(array $data, string $offlineField = 'mblock_offline'): array
MBlock::isOnline(array $item, string $offlineField = 'mblock_offline'): bool
MBlock::isOffline(array $item, string $offlineField = 'mblock_offline'): bool$filter ist 'all', 'online' oder 'offline'. Ein Block gilt als offline, wenn das Offline-Feld '1', true oder 'true' enthält.
$items = MBlock::getOnlineDataArray("REX_VALUE[1]");
foreach ($items as $item) {
echo rex_escape($item['name'] ?? '');
}Einzelne Blöcke lassen sich mit isOnline() und isOffline() prüfen (seit 4.7.0):
foreach (MBlock::getDataArray("REX_VALUE[1]") as $item) {
if (MBlock::isOnline($item)) {
echo rex_escape($item['name'] ?? '');
}
}MBlock::filterByField(array $items, string $field, mixed $value, bool $strict = false): array
MBlock::sortByField(array $items, string $field, string $direction = 'asc'): array
MBlock::groupByField(array $items, string $field): array
MBlock::limitItems(array $items, int $limit, int $offset = 0): arrayfilterByFieldvergleicht mit==, bei$strict = truemit===. Blöcke ohne das Feld fallen weg. Für mehrere Werte mehrmals filtern oderarray_filternutzen.sortByFieldsortiert numerisch, wenn beide Werte numerisch sind, sonst mitstrcasecmp.$directionist'asc'oder'desc'. Die Schlüssel werden neu nummeriert.groupByFieldliefert[feldwert => [blöcke]]; fehlt das Feld, landet der Block unter'undefined'.limitItemsist einarray_slice.
$items = MBlock::getOnlineDataArray("REX_VALUE[1]");
$news = MBlock::filterByField($items, 'category', 'news');
$news = MBlock::sortByField($news, 'date', 'desc');
$top = MBlock::limitItems($news, 5);
foreach (MBlock::groupByField($items, 'category') as $category => $group) {
echo '<h2>' . rex_escape($category) . '</h2>';
}MBlock::generateSchema(array $items, string $type = 'Article', array $fieldMapping = []): stringLiefert ein fertiges <script type="application/ld+json"> mit einem @graph aus allen Blöcken, oder einen leeren String, wenn kein Block ein passendes Feld hat. Für Article, Product und Event gibt es Standard-Zuordnungen (z. B. headline aus title, name oder headline); $fieldMapping ergänzt oder überschreibt sie. Werte aus REX_MEDIA_*- und REX_LINK_*-Feldern werden in absolute URLs umgewandelt.
echo MBlock::generateSchema($items, 'Person', [
'name' => 'name',
'jobTitle' => 'position',
'image' => 'REX_MEDIA_1',
]);mblock_rex_form ist ein rex_form, dessen Felder als MBlock gerendert werden können. Der Extension Point REX_FORM_SAVED (in boot.php registriert) speichert die Blöcke als JSON.
$form = mblock_rex_form::factory('rex_meine_tabelle', '', 'id = ' . $id);
$form->addTextField('title');
echo MBlock::show('rex_meine_tabelle::mblock_field', $form);Für YForm lautet die Id 'yform::tabelle::feld'; das Formular ist das rex_yform-Objekt.
Das gesamte Verhalten steckt in assets/mblock.js (jQuery). boot.php lädt mblock.js, solange der Debug-Modus oder das Debug-Addon aktiv ist, sonst mblock.min.js. Die Datei enthält:
- Initialisierung und Sortable-Anbindung (
mblock_init,mblock_init_sort,mblock_sort) - Reindex der Feldnamen, Ids und Widget-Buttons je Block (
mblock_reindex) - Hinzufügen, Löschen, Verschieben (
mblock_add_item,mblock_remove_item,mblock_move) - Kopieren/Einfügen (
MBlockClipboard) und Online/Offline (MBlockOnlineToggle) - Popup-Bridges für
REX_MEDIA,REX_MEDIALIST,REX_LINK,REX_LINKLIST, damit die Widget-Buttons neuer Blöcke mit der richtigen Id arbeiten - Submit-Handler, der TinyMCE- und CKEditor-5-Inhalte in die Textareas schreibt
| Funktion | Beschreibung |
|---|---|
mblock_init($wrapper) |
Initialisiert einen .mblock_wrapper (wird für alle Wrapper bei rex:ready aufgerufen) |
mblock_init_sort($wrapper) |
Reindex und Sortable neu aufbauen |
mblock_add($wrapper) |
Klick-Handler der Buttons binden |
mblock_reindex($wrapper) |
Namen (][n][), Ids und Widget-Onclicks aller Blöcke neu vergeben |
mblock_smooth_scroll_to_element(el) |
Sanft zu einem Element scrollen |
Beim Minifizieren bleiben mblock_init, mblock_init_sort, mblock_sort, mblock_add, MBlockClipboard, MBlockOnlineToggle und mblock_smooth_scroll_to_element unter ihrem Namen erhalten; alle anderen Namen können sich in mblock.min.js ändern.
Kopieren legt den Block als HTML mit eingefrorenen Werten in der sessionStorage ab (Schlüssel mblock_clipboard, Inhalt {html, moduleType, timestamp}). Eingefügt werden kann nur im selben Modul; moduleType wird aus module_id des Slice-Formulars gebildet.
| Methode | Beschreibung |
|---|---|
MBlockClipboard.copy($wrapper, $item) |
Block kopieren |
MBlockClipboard.paste($wrapper, $afterItem) |
Nach $afterItem einfügen, sonst am Anfang |
MBlockClipboard.clear() |
Zwischenablage leeren |
MBlockClipboard.updatePasteButtons() |
Einfügen-Buttons je nach Modultyp aktivieren |
| Methode | Beschreibung |
|---|---|
MBlockOnlineToggle.toggle($wrapper, $item) |
Block umschalten (nutzt den Button .mblock-offline-toggle-btn im Block) |
MBlockOnlineToggle.setOfflineState($item, isOffline) |
Hidden-Feld mblock_offline und Klasse .mblock-offline setzen |
MBlock feuert jQuery-Events auf den Blöcken:
| Event | Wann |
|---|---|
rex:ready (mit dem Block als Argument) |
Neuer, eingefügter oder verschobener Block ist im DOM; hier initialisieren Addons ihre Widgets (TinyMCE, CKEditor 5, Selectpicker, ...) |
mblock:change (mit dem Block als Argument) |
Nach dem Hinzufügen und nach dem Verschieben |
change auf allen Feldern |
Nach dem Einfügen aus der Zwischenablage |
$(document).on('rex:ready', function (e, container) {
container.find('.mein-widget').each(function () { /* init */ });
});Beim Klonen eines Blocks vergibt mblock_reindex neue Widget-Ids (REX_MEDIA_<id>, REX_LINK_<id>, ...) und schreibt sie in die onclick-Attribute der Buttons um. Die Popup-Bridges fangen die Klicks zusätzlich per Event-Delegation ab und lesen die Id aus dem Block, sodass auch Buttons ohne aktualisiertes onclick das richtige Feld treffen. Editor-Felder (.tiny-editor, .cke5-editor) behalten ihre id, solange sie eindeutig ist; sonst bekommen sie eine neue eindeutige Id.
Templates liegen in redaxo/data/addons/mblock/templates/<name>/ und werden in den Einstellungen ausgewählt. Mitgeliefert sind standard, modern, akg_skin und retro_8bit.
templates/mein_theme/
├── template.ini # Name und Beschreibung
├── mblock_wrapper.ini # Container
├── mblock_element.ini # ein Block
└── mein_theme.css # optional, gleicher Name wie der Ordner
Tags in mblock_wrapper.ini:
| Tag | Inhalt |
|---|---|
<mblock:settings/> |
data-*-Attribute aus den Optionen |
<mblock:copy_paste_toolbar/> |
Leiste "Zwischenablage leeren" |
<mblock:output/> |
Alle Blöcke |
Optional darf der Wrapper einen Hinzufügen-Streifen unter der Liste enthalten (Templates modern und retro_8bit); der Button hängt einen neuen Block ans Ende und wird bei max gesperrt:
<div class="mblock-add-bar"><button type="button" class="btn btn-default mblock-add-last">{{mblock::mblock_add_element}}</button></div>Tags in mblock_element.ini:
| Tag | Inhalt |
|---|---|
<mblock:form/> |
Formular des Blocks |
<mblock:index/> |
Index des Blocks |
<mblock:offline_class/> |
mblock-offline, wenn der Block offline ist |
<mblock:offline_button/> |
Online/Offline-Button |
<mblock:copy_paste_buttons/> |
Kopieren/Einfügen-Buttons |
{{mblock::sprachschluessel}} |
Übersetzung aus den MBlock-Sprachdateien |
Die Buttons erkennt das JavaScript an ihren Klassen: .addme, .removeme, .moveup, .movedown, .mblock-copy-btn, .mblock-paste-btn, .mblock-offline-toggle-btn; der Griff ist .sorthandle, jeder Block ist div.sortitem.
Farben, Abstände und Effekte des Standard-Stylesheets sind CSS-Variablen (--mblock-*), siehe README "Templates & Theming".
MBlock registriert REX_FORM_SAVED (für mblock_rex_form). Eigene Extension Points bietet MBlock nicht; für eigene Logik nach dem Speichern eignen sich die REDAXO-Extension-Points SLICE_UPDATED, SLICE_ADDED und REX_FORM_SAVED.
assets/mblock.js ist die einzige Quelldatei. build/minify.js erzeugt daraus mit Terser assets/mblock.min.js:
cd redaxo/src/addons/mblock/build
npm install
node minify.js # oder ./build.shDanach in der REDAXO-Installation assets:sync bzw. den Asset-Cache leeren, damit die neue Datei ausgeliefert wird.
Input:
<?php
use FriendsOfRedaxo\MForm;
use FriendsOfRedaxo\MBlock\MBlock;
$id = 1;
$mform = MForm::factory()
->addFieldsetArea('Card', MForm::factory()
->addTextField("$id.0.title", ['label' => 'Titel'])
->addSelectField("$id.0.category", ['news' => 'News', 'events' => 'Events'], ['label' => 'Kategorie'])
->addTextAreaField("$id.0.text", ['label' => 'Text'])
->addMediaField(1, ['label' => 'Bild'])
->addHiddenField("$id.0.mblock_offline", '0')
);
echo MBlock::show($id, $mform, ['min' => 1, 'max' => 5]);Output:
<?php
use FriendsOfRedaxo\MBlock\MBlock;
$items = MBlock::getOnlineDataArray("REX_VALUE[1]");
foreach (MBlock::groupByField($items, 'category') as $category => $group) {
echo '<h2>' . rex_escape($category) . '</h2>';
foreach (MBlock::sortByField($group, 'title') as $item) {
echo '<article><h3>' . rex_escape($item['title'] ?? '') . '</h3>';
if (($file = $item['REX_MEDIA_1'] ?? '') && ($media = rex_media::get($file))) {
echo '<img src="' . rex_media_manager::getUrl('rex_media_medium', $media->getFileName()) . '" alt="' . rex_escape($media->getTitle()) . '">';
}
echo '<p>' . nl2br(rex_escape($item['text'] ?? '')) . '</p></article>';
}
}
echo MBlock::generateSchema($items, 'Article', ['headline' => 'title', 'articleBody' => 'text']);<?php
use FriendsOfRedaxo\MBlock\MBlock;
$id = 1;
$form = <<<EOT
<fieldset class="form-horizontal">
<div class="form-group">
<div class="col-sm-2 control-label"><label>Name</label></div>
<div class="col-sm-10"><input type="text" name="REX_INPUT_VALUE[$id][0][name]" value="" class="form-control"></div>
</div>
<div class="form-group">
<div class="col-sm-2 control-label"><label>Foto</label></div>
<div class="col-sm-10">REX_MEDIA[id="1" widget="1"]</div>
</div>
<input type="hidden" name="REX_INPUT_VALUE[$id][0][mblock_offline]" value="0">
</fieldset>
EOT;
echo MBlock::show($id, $form);Weitere Beispiele: Addons > MBlock > Demo.