Neos, Editor UX, CodeDynamische Placeholder im Inline-Editor

Jon Uhlmann

Jon Uhlmann

In der yaml Konfiguration von einer Property, welches Inline mit dem CKEditor bearbeitet werden kann, kann ein Placeholder definiert werden. Dies sollte auch immer gesetzt werden, weil sonst der Editor nicht weiss, wo er genau klicken soll, um den Text zu bearbeiten. Was ist aber wenn man zum Beispiel bei einer Seite den Titel der Seite ausgeben will, diesen aber überschreibbar gestalten will? Klar, man kann es im Inspektor lösen, aber wäre es nicht besser, den Titel der Seite als Placeholder auszugeben? Leider ist das mit den Bordmittel mit Neos nicht möglich, aber mit ein bisschen Fusion und Javascript ist dies möglich.

Da der Editor zu einem unbestimmten Zeitpunkt die Attribute setzt, muss erst wenn er dies erledigt hat das Attribute data-neos-placeholder oder data-placeholder (ab Neos UI Version 8.3.1) ersetzt werden. Eine Variante wäre dies mit einem setTimeout zu erledigen, da ist jedoch nicht wirklich sicher, ob der Browser schon fertig ist. Oder aber der Benutzer sieht zu lange den im yaml definierten Placeholder. Aber wie kann das sauber gelöst werden? Eigentlich ganz einfach: Mit einem Observer, welcher die Attribute beobachtet und darauf reagiert. Neben dem muss der Prototype Neos.Neos:Editable um die Möglichkeit den Placeholder sowie einen Fallback zu setzten erweitert werden.

Lass uns zuerst mit der Erweiterung von dem Editable beginnen:

 Neos.Neos:Editable wird hierbei um zwei neue Properties erweitert: fallback und placeholder. Dabei wird per default placeholder auf den gleichen Wert wie fallback gesetzt. Damit kann einfach der Fallback gesetzt werden, welches 99% der benötigten Requirements abdeckt. Beim Placeholder werden <br> zu Zeilenumbrüchen konvertiert und anschliessend werden alle HTML-Tags entfernt. Der Fallback wird mit einem einfach @process erledigt.

Anschliessend wird folgendes Javascript im Backend eingebunden. Dieses prüft, ob ein Placeholder, ein dynamischer Placeholder und ob diese beiden Werte nicht den gleichen Wert haben und setzt gegebenenfalls den dynamischen Placeholder als Placeholder.

Ab Neos UI in der Version 8.3.1 sieht der Code folgendermassen aus:

function observerCallback(mutationList) {
    for (const mutation of mutationList) {
        if (mutation.type !== "attributes") {
            return;
        }
        const target = mutation.target;
        const placeholderTarget = target.querySelector("[data-placeholder]");

        if (!placeholderTarget) {
            return;
        }

        const override = target.getAttribute("data-neos-placeholder-override");
        const original = placeholderTarget.getAttribute("data-placeholder");

        if (!original || !override || original === override) {
            return;
        }

        console.log(`Override placeholder '${original}' with '${override}'`);
        placeholderTarget.setAttribute("data-placeholder", override);
    }
}

const observer = new MutationObserver(observerCallback);

const config = {
    childList: true,
    subtree: true,
    attributes: true,
    attributeOldValue: true,
    attributeFilter: ["data-neos-placeholder-override"],
};

observer.observe(document.body, config);

Und darunter folgendermassen:

function observerCallback(mutationList) {
    for (const mutation of mutationList) {
        if (mutation.type !== "attributes") {
            return;
        }
        const target = mutation.target;
        const original = target.getAttribute("data-neos-placeholder");
        const override = target.getAttribute("data-neos-placeholder-override");

        if (!original || !override || original === override) {
            return;
        }

        target.setAttribute("data-neos-placeholder", override);
    }
}

const observer = new MutationObserver(observerCallback);

const config = {
    childList: true,
    subtree: true,
    attributes: true,
    attributeOldValue: true,
    attributeFilter: ["data-neos-placeholder-override"],
};

observer.observe(document.body, config);

Nun kann zum Beispiel bei einem Dokument der Titel der Seite als Fallback Wert gesetzt werden. Falls der Editor einen anderen Text setzt, wird dieser natürlich im Frontend ausgegeben, andernfalls wird der Fallback verwendet.

headline = Neos.Neos:Editable {
    property = 'headline'
    block = false
    fallback = ${q(node).property('title')}
}

Ich hoffe, ich konnte dir mit diesem kleinen Trick helfen. Ich würde mich natürlich über ein kleines Feedback via Twitter freuen. Have fun!