diff --git a/grails-doc/src/en/guide/reference.adoc b/grails-doc/src/en/guide/reference.adoc index 7597ccb630d..e69395e216a 100644 --- a/grails-doc/src/en/guide/reference.adoc +++ b/grails-doc/src/en/guide/reference.adoc @@ -1138,6 +1138,11 @@ include::ref/Tags/fieldValue.adoc[] include::ref/Tags/findAll.adoc[] +[[ref-tags-flashMessages]] +==== flashMessages + +include::ref/Tags/flashMessages.adoc[] + [[ref-tags-form]] ==== form diff --git a/grails-doc/src/en/guide/upgrading/upgrading71x.adoc b/grails-doc/src/en/guide/upgrading/upgrading71x.adoc index a8511a90435..0ec9b20439f 100644 --- a/grails-doc/src/en/guide/upgrading/upgrading71x.adoc +++ b/grails-doc/src/en/guide/upgrading/upgrading71x.adoc @@ -721,4 +721,25 @@ If this causes issues with existing URL mappings, you can disable it in `applica grails: urlmapping: validateWildcards: false ----- \ No newline at end of file +---- + +===== 2.10 Flash Messages Tag (`g:flashMessages`) + +Grails 7.1 introduces a new `` tag that renders `flash.message`, `flash.error`, and `flash.warning` as Bootstrap 5 dismissible alerts with appropriate styling (success, danger, and warning respectively). + +The tag automatically prevents duplicate rendering -- if called in both a page and its layout, only the first invocation produces output. All flash content is HTML-encoded to prevent XSS. + +The default layout (`main.gsp`) and all scaffolding templates now use this tag. If your application has custom views that render flash messages manually, you can replace the boilerplate: + +[source,xml] +---- +<%-- Before --%> + + + + +<%-- After --%> + +---- + +The tag supports optional attributes for customizing CSS classes, icons, ARIA role, and dismissibility. See the {gspTagsRef}flashMessages.html[flashMessages] tag reference for full details. diff --git a/grails-doc/src/en/ref/Tags - GSP/flashMessages.adoc b/grails-doc/src/en/ref/Tags - GSP/flashMessages.adoc new file mode 100644 index 00000000000..a469654a0b8 --- /dev/null +++ b/grails-doc/src/en/ref/Tags - GSP/flashMessages.adoc @@ -0,0 +1,120 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +//// + + +== flashMessages + + + +=== Purpose + + +Renders `flash.message`, `flash.error`, and `flash.warning` as dismissible Bootstrap alert divs with appropriate styling. Automatically prevents duplicate rendering when used in both pages and layouts. + + +=== Examples + + +Basic usage in a view: + +[source,xml] +---- + +---- + +In a layout (safe to use alongside page-level usage -- the tag skips rendering if already called): + +[source,xml] +---- + + +---- + +Non-dismissible alerts: + +[source,xml] +---- + +---- + +Custom ARIA role: + +[source,xml] +---- + +---- + +Custom styling for success messages: + +[source,xml] +---- + +---- + +Setting flash messages in a controller: + +[source,groovy] +---- +// Success message (renders as green alert) +flash.message = "Book '${book.title}' saved successfully." + +// Error message (renders as red alert) +flash.error = "Unable to delete the record." + +// Warning message (renders as yellow alert) +flash.warning = "You have unsaved changes." +---- + + +=== Description + + +The `flashMessages` tag renders any combination of `flash.message`, `flash.error`, and `flash.warning` as Bootstrap 5 alert divs. Each flash key maps to a different alert style: + +[cols="1,2,2"] +|=== +| Flash Key | Default Alert Class | Default Icon + +| `flash.message` +| `alert alert-success alert-dismissible fade show` +| `bi bi-check-circle me-2` + +| `flash.error` +| `alert alert-danger alert-dismissible fade show` +| `bi bi-exclamation-triangle me-2` + +| `flash.warning` +| `alert alert-warning alert-dismissible fade show` +| `bi bi-exclamation-circle me-2` +|=== + +The tag automatically sets a `_flashRendered` request attribute after rendering. On subsequent calls within the same request, the tag detects this attribute and outputs nothing. This allows both pages and layouts to include `` without producing duplicate alerts -- whichever renders first wins. + +All flash message content is HTML-encoded to prevent XSS. + +Attributes + +* `messageClass` (optional) - CSS class for `flash.message` alerts. Default: `alert alert-success alert-dismissible fade show` +* `messageIcon` (optional) - Icon class for `flash.message` alerts. Default: `bi bi-check-circle me-2` +* `errorClass` (optional) - CSS class for `flash.error` alerts. Default: `alert alert-danger alert-dismissible fade show` +* `errorIcon` (optional) - Icon class for `flash.error` alerts. Default: `bi bi-exclamation-triangle me-2` +* `warningClass` (optional) - CSS class for `flash.warning` alerts. Default: `alert alert-warning alert-dismissible fade show` +* `warningIcon` (optional) - Icon class for `flash.warning` alerts. Default: `bi bi-exclamation-circle me-2` +* `role` (optional) - ARIA role for alert divs. Default: `alert` +* `dismissible` (optional) - Whether to show a close button. Default: `true` diff --git a/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp b/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp index 96309fbd74c..214d932b782 100644 --- a/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp +++ b/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp @@ -21,6 +21,7 @@
+
diff --git a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ApplicationTagLib.groovy b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ApplicationTagLib.groovy index bd14789f672..69699217d9e 100644 --- a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ApplicationTagLib.groovy +++ b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ApplicationTagLib.groovy @@ -471,4 +471,68 @@ class ApplicationTagLib implements ApplicationContextAware, InitializingBean, Gr // encoding is handled in GroovyPage.invokeTag and GroovyPage.captureTagOutput body() } + + /** + * Renders flash.message, flash.error, and flash.warning as Bootstrap alert divs. + * Automatically skips rendering if already called during this request, preventing + * duplicate display when used in both pages and layouts. + * + * @emptyTag + * + * @attr messageClass CSS class for flash.message alerts (default: 'alert alert-success alert-dismissible fade show') + * @attr messageIcon Icon class for flash.message alerts (default: 'bi bi-check-circle me-2') + * @attr errorClass CSS class for flash.error alerts (default: 'alert alert-danger alert-dismissible fade show') + * @attr errorIcon Icon class for flash.error alerts (default: 'bi bi-exclamation-triangle me-2') + * @attr warningClass CSS class for flash.warning alerts (default: 'alert alert-warning alert-dismissible fade show') + * @attr warningIcon Icon class for flash.warning alerts (default: 'bi bi-exclamation-circle me-2') + * @attr role ARIA role for alert divs (default: 'alert') + * @attr dismissible Whether to show a close button (default: true) + */ + Closure flashMessages = { attrs -> + if (request.getAttribute('_flashRendered')) { + return + } + + boolean rendered = false + boolean dismissible = attrs.dismissible != null ? attrs.dismissible.toString().toBoolean() : true + String role = attrs.role ?: 'alert' + + if (flash.message) { + renderFlashAlert( + attrs.messageClass ?: 'alert alert-success alert-dismissible fade show', + attrs.messageIcon ?: 'bi bi-check-circle me-2', + flash.message, dismissible, role) + rendered = true + } + + if (flash.error) { + renderFlashAlert( + attrs.errorClass ?: 'alert alert-danger alert-dismissible fade show', + attrs.errorIcon ?: 'bi bi-exclamation-triangle me-2', + flash.error, dismissible, role) + rendered = true + } + + if (flash.warning) { + renderFlashAlert( + attrs.warningClass ?: 'alert alert-warning alert-dismissible fade show', + attrs.warningIcon ?: 'bi bi-exclamation-circle me-2', + flash.warning, dismissible, role) + rendered = true + } + + if (rendered) { + request.setAttribute('_flashRendered', true) + } + } + + private void renderFlashAlert(String cssClass, String icon, Object message, boolean dismissible, String role) { + out << "
" + out << "" + out << message.toString().encodeAsHTML() + if (dismissible) { + out << '' + } + out << '
' + } } diff --git a/grails-profiles/web/skeleton/grails-app/views/layouts/main.gsp b/grails-profiles/web/skeleton/grails-app/views/layouts/main.gsp index 96309fbd74c..214d932b782 100644 --- a/grails-profiles/web/skeleton/grails-app/views/layouts/main.gsp +++ b/grails-profiles/web/skeleton/grails-app/views/layouts/main.gsp @@ -21,6 +21,7 @@
+
diff --git a/grails-scaffolding/src/main/templates/scaffolding/create.gsp b/grails-scaffolding/src/main/templates/scaffolding/create.gsp index 6932ed21a75..44b96f859b7 100644 --- a/grails-scaffolding/src/main/templates/scaffolding/create.gsp +++ b/grails-scaffolding/src/main/templates/scaffolding/create.gsp @@ -24,9 +24,7 @@

- -
\${flash.message}
-
+