Thymeleaf Template Engine

Nur ganz kurz: wenn es sich um eine andere Sicht auf eine Ressource handelt, dann ist ein Request-Parameter die richtige Wahl. So wie ?view=admin

[QUOTE=cmrudolph]Wenn man folgende Dependency in die pom.xml einfügt, dann funktioniert der Code aus master:
[xml]
org.springframework.data
spring-data-commons
1.10.1.BUILD-SNAPSHOT
[/xml][/QUOTE]

Mittlerweile ist das erste Service-Release für den Fowler-Release-Train draußen, sodass die Dependency zum Snapshot raus kann. Die Version kann also auf 1.10.1.RELEASE angepasst werden.

Danke, ist erledigt.

Ich habe gerade breadcrumbs in forum.html und topic.html implementiert. Allerdings habe ich ums Verrecken die Rekursion (also category.parent, davon den parent u.s.w.) in Thymeleaf nicht hinbekommen. Ich habe Beispiele im Netz gesehen, wo das mit Fragmenten funktionieren soll, aber bei mir kam immer der Fehler, dass die Variable null ist - ganz so, als ob Thymeleaf gar nicht interessiert, dass die entsprechende Stelle in einem Fragment steht, was die Variable als Parameter übergeben bekommen soll. Ich habe es jetzt einfach als Liste im Model mit übergeben, aber falls es dich interessiert, kannst du es dir ja mal anschauen.

Ich hätte die rekursive Struktur von vornherein für die Darstellungsschicht aufgebrochen, denn selbst wenn Rekursion direkt im Template möglich wäre, wäre mir das schon zu viel Logik (für Rekursion ist ja immer etwas wie eine Funktion notwendig).
Dass bei einem Ansatz mit Fragmenten ständig null ankommt, kann daran liegen, dass man die Variablennamen auf eine bestimmte Art und Weise übergeben muss. Falls du den Ansatz noch einmal verfolgen möchtest, kannst du ja die zwei Templates hier posten.

Wenn du auch meinst, dass das zuviel Logik auf Template-Seite wäre, würde ich es so belassen.

Als nächstes würde ich das Forum soweit bringen, dass man Threads erstellen, einfache Text-Antworten geben kann, und Mods grundlegende Bearbeitungsmöglichkeiten haben. Feinheiten (Text-Formatierung, Paginierung, sticky oder geschlossene Threads, Schachtelungstiefe von Unterforen u.s.w.) würde ich dann erst mal zurückstellen. Ich würde danach lieber nochmal das Rechte-System überarbeiten (siehe Rechte-Thread) und schauen, wie wir die vorhandene Struktur REST-artiger gestalten (aber ganz ohne vorhandenes Grundgerüst diskutiert sich sowas schlecht).

Mir ist durch Zufall aufgefallen, dass man die Form-Objekte im Model gar nicht als Attribut übergeben muss. Ich habe alles getestet, selbst nach maven clean, und auch die Validators funktionieren noch. Trotzdem ist das schon ziemlich starker Tobak. Woher bekommt z.B. th:object seinen Wert?

Hast du ein Template, an dem wir gezielt diskutieren können? Mir ist letztens beim querlesen eines Templates aufgefallen, dass einiges noch nicht im “Thymeleaf-Stil” ist. (z. B. Links mit @{}-Notation).

OK, das war vielleicht keine gute Beschreibung. In ForumController zeige ich die normale Forumsseite jetzt so:

    @RequestMapping(value = "/cat", method = RequestMethod.GET)
    public String showCategory(
            Model model,
            @RequestParam Optional<Long> catId) {
        Category category = catId.
                flatMap(categoryService::getById).
                orElseGet(categoryService::getRootCategory);
        model.addAttribute("category", category);
        model.addAttribute("breadcrumbs", breadcrumbs(category.getParent()));
        return FORUM_PAGE;
    }

Ich setze nicht wie früher ein CategoryCreateForm in den Model-Attributen. In forum.html benutze ich weiterhin


<div class="panel panel-default"
     th:if="${currentUser} and ${currentUser.hasRoleOrPermission('ACCESS_MODERATION_PAGE')}">
    <div class="panel-heading" th:text="#{forum.newCategory}">New Category</div>
    <div class="panel-body">
        <form role="form" th:action="${#mvc.url('FC#handleCategoryCreateForm').build()}"
              th:object="${categoryCreateForm}" method="post">
            <div class="row">
                <div class="col-md-5">
                    <input type="hidden" name="parentId" th:value="${category.id}"/>
                    <label for="name" th:text="#{forum.replyContent}">Category Name</label>
                    <input class="form-control" type="text" id="name" name="name"/>
                    <br/>
                    <button type="submit" class="btn btn-primary" th:text="#{forum.newCategoryButton}">Button-Title
                    </button>
                </div>
            </div>
        </form>
    </div>
</div>

und kann dann wieder im ForumController das CategoryCreateForm auslesen:

    @RequestMapping(value = "/categoryCreate", method = RequestMethod.POST)
    public String handleCategoryCreateForm(
            @Valid CategoryCreateForm categoryCreateForm,
            BindingResult bindingResult,
            RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors()) {
            FlashMessage.ERROR.put(redirectAttributes, "category.create.failure");
        } else {
            try {
                categoryService.createNewCategory(categoryCreateForm);
                FlashMessage.SUCCESS.put(redirectAttributes, "category.create.success");
            } catch (DataIntegrityViolationException e) {
                FlashMessage.ERROR.put(redirectAttributes, "category.create.failure");
            }
        }
        return redirectToCategory(categoryCreateForm.getParentId());
    }

Ich dachte immer, das CategoryCreateForm müsste in den Attributen mitgegeben werden, denn es sieht ja so aus, als ob mit th:object="${categoryCreateForm}" von Thymeleaf darauf zugegriffen wird.

Aber da scheine ich den Mechanismus nicht richtig verstanden zu haben, deshalb meine Frage.

Das ist eine weitere Stelle, die mir mal kurz aufgefallen war (hatte da keine Zeit einen Beitrag zu schreiben, dann hab ich 's vergessen…). Du machst kein Formbinding!
Statt th:value und th:name musst du bei den Formularelementen th:field="*{fieldname}" verwenden.
In diesem Fall erscheint es auf den ersten Blick so, als ob es funktioniert, weil die Variable, die du mit th:value “bindest” nicht gesetzt und daher als leerer String übergeben wird. Sobald aber in dem Formular was drinsteht, funktioniert das ggf. nicht so, wie gewünscht.
Nachlesen kannst du das hier: Tutorial: Thymeleaf + Spring#inputs

Ich versuche mich gerade dran, aber ich bekomme auf einmal die Hidden-Fields nicht mehr gesetzt. Z.B. hat <input type="hidden" th:field="*{userId}" th:value="${user.id}"/> immer den Wert 0L, obwohl auf die Variable user nur ein paar Zeilen weiter oben erfolgreich zugegriffen wurde.

Das th:value brauchst du bei der Verwendung von th:field nicht mehr. Ebensowenig brauchst du noch ein name-Attribut. Die beiden Attribute werden gemeinsam durch th:field gesetzt. Dadurch, dass du jetzt noch ein weiteres th:value gesetzt hast, kann ich mir vorstellen, dass der Wert von der (nicht gebundenen?) Variablen user.id ständig gesetzt wird.

Aber ich muss dem Ding doch irgenwie auch einen externen Wert **zuweisen **können? An das Form selber kann ich das nicht zuweisen, denn an der entsprechenden Stelle wird das Form in einer Schleife verwendet (Rechte an Nutzer zuweisen im Admin-Bereich). Oder muss man da etwa für jeden Nutzer ein eigenes Form basteln - das wäre aber ganz schön Overkill, oder?

Sorry wenn ich mich hier etwas anstelle, aber momentan fehlt mir irgendwie noch der Durchblick.

Wenn du ein Form-Binding machen möchtest, dann brauchst du selbstverständlich auch ein DTO / Formularobjekt - sonst hättest du ja nichts, an das du binden kannst.
Dabei muss in dem Formular allerdings nicht jede Property enthalten sein, die im DTO * ist. Du kannst für auch Validierungsgruppen festlegen, sodass du selbst bei der Validierung keine Probleme mit Formularen hast, die nicht alle Properties enthalten.
Das Form-Binding dient ja dem Zweck, dass dein Controller bereits das Javaobjekt geliefert bekommt und du nicht erst umständlich aus den POST-Parametern ein Objekt zusammenbasteln musst.

  • das DTO kann natürlich auch ein “POJO” sein. Welche Sichtbarkeit von Properties (in Form von Gettern) für Thymeleaf erforderlich ist, weiß ich gerade nicht aus dem Kopf. Welche Sichtbarkeit für das Form-Binding in Spring erforderlich ist, müsste ich auch nachsehen.

Habe erst mal mit Formbinding in einfachen Fällen (TopicCreateForm, CategoryCreateForm) angefangen, und das hat den Code auch vereinfacht.

Aber nochmal die Frage: Was ist, wenn ich z.B. eine Tabelle habe, und in jeder Zeile ist ein Formular, das die gleiche Aktion (natürlich mit unterschiedlichen Werten) ausführen soll. Würde man dann wirklich für jede einzelne Zeile ein eigenes Formular fertig mitliefern? Oder doch nachträglich (bei der Seitengenerierung) eine einzige Formularvorlage je nach zeilenspezifischen Daten manipulieren?

Iterativ Formulare zu generieren funktioniert leider nicht. Das Problem hatte ich auch schon. Ich hatte einen praktikablen Workaround gefunden, habe aber bis mindestens Anfang nächster Woche keinen Zugriff auf meinen Quellcode.