From 5b8f67bef06715f72eb6986bfab226cfa1d129d7 Mon Sep 17 00:00:00 2001 From: Nick Cervantes Date: Wed, 15 Jul 2026 16:53:35 +0800 Subject: [PATCH 1/3] [twgit] Init feature 'feature-PRESIDECMS-3315_webflow_allow_custom_action_buttons_without_overiding_entire_layout'. From b19595c85ff5fd8a9bd768befdfcc657b45b211b Mon Sep 17 00:00:00 2001 From: Nick Cervantes Date: Thu, 16 Jul 2026 14:31:06 +0800 Subject: [PATCH 2/3] PRESIDECMS-3315: Apply new extra button option to webflow actions. --- .../webflowlayout/adminWebflowLayout.less | 47 ++++++++++------ system/handlers/webflow/Default.cfc | 54 +++++++++++++++++-- system/i18n/webflow.properties | 7 ++- system/views/webflow/default/actions.cfm | 10 +++- system/views/webflow/default/cancelButton.cfm | 9 +++- system/views/webflow/default/extraButton.cfm | 23 ++++++++ system/views/webflow/default/nextButton.cfm | 11 +++- system/views/webflow/default/prevButton.cfm | 9 +++- 8 files changed, 140 insertions(+), 30 deletions(-) create mode 100644 system/views/webflow/default/extraButton.cfm diff --git a/system/assets/css/admin/specific/webflowlayout/adminWebflowLayout.less b/system/assets/css/admin/specific/webflowlayout/adminWebflowLayout.less index 53f11fd623..c7fa933620 100755 --- a/system/assets/css/admin/specific/webflowlayout/adminWebflowLayout.less +++ b/system/assets/css/admin/specific/webflowlayout/adminWebflowLayout.less @@ -2,35 +2,48 @@ .preside-theme { .webflow-layout { .webflow-step-title { - margin-top: 40px; - margin-bottom: 30px; - padding-bottom: 6px; - border-bottom: 1px solid #e2e2e2; - font-size: 22px; - font-weight: normal; + margin-top : 40px; + margin-bottom : 30px; + padding-bottom : 6px; + border-bottom : 1px solid #e2e2e2; + font-size : 22px; + font-weight : normal; } .webflow-action-buttons { - border-top: 1px solid #e2e2e2; - padding-top: 20px; - margin-top: 20px; + display : flex; + flex-direction : row; + flex-wrap : wrap; + align-items : center; + gap : 10px; + border-top : 1px solid #e2e2e2; + padding-top : 20px; + margin-top : 20px; - .webflow-cancel-btn-container, - .webflow-prev-btn-container { - float: left; + .webflow-cancel-btn-container { .webflow-cancel-btn { - padding: 12px 6px; - display : inline-block; + padding : var( --button-padding-vertical, 7px ) var( --button-padding-horizontal, 10px ); } + } + + .webflow-extra-btn-container { + + &-standalone { + margin-left : auto; + } + + &-next { + margin-left : auto; - .webflow-prev-btn { - margin-right: 10px; + + .webflow-next-btn-container { + margin-left : 0; + } } } .webflow-next-btn-container { - float: right; + margin-left : auto; } } diff --git a/system/handlers/webflow/Default.cfc b/system/handlers/webflow/Default.cfc index 09b5f4bfad..3077b1eebf 100755 --- a/system/handlers/webflow/Default.cfc +++ b/system/handlers/webflow/Default.cfc @@ -219,25 +219,69 @@ component { } private string function actions( event, rc, prc, args={} ){ - var extraQs = ""; - var useAjaxLayout = isTrue( args.useAjaxLayout ?: false ); + var extraQs = ""; + var useAjaxLayout = IsTrue( args.useAjaxLayout ?: "" ); + var additionalAction = IsStruct( args.additionalAction ?: "" ) ? Duplicate( args.additionalAction ) : {}; + var additionalHandler = Trim( args.additionalActionHandler ?: "" ); + var additionalHandlerResult = ""; + var additionalActionHref = ""; + var additionalActionUrlValue = ""; + var webflowActionQs = ""; if ( useAjaxLayout ) { extraQs = "&args.useAjaxLayout=true&_rurl=#args.returnUrl ?: ""#"; } + webflowActionQs = "csrfToken=#event.getCsrfToken()#&_wid=#UrlEncode( args.obfuscatedFields )##extraQs#"; + if ( IsTrue( args.hasNext ?: "" ) && IsFalse( args.disableNext ?: "" ) ) { args.nextButton = renderViewlet( event="webflow.default.nextButton", args=args ); } - if ( IsTrue( args.hasPrev ?: "" ) && IsFalse( args.disablePrev ?: "" ) ) { - args.prevLink = args.prevLink ?: event.buildLink( linkto="webflow.default.backAction", queryString="csrfToken=#event.getCsrfToken()#&_wid=#UrlEncode( args.obfuscatedFields )##extraQs#" ); + if ( IsTrue( args.hasPrev ?: "" ) && IsFalse( args.disablePrev ?: "" ) ) { + args.prevLink = args.prevLink ?: event.buildLink( linkto="webflow.default.backAction", queryString=webflowActionQs ); args.prevButton = renderViewlet( event="webflow.default.prevButton", args=args ); } if ( IsTrue( args.canCancel ?: "" ) ) { - args.cancelLink = args.cancelLink ?: event.buildLink( linkto="webflow.default.cancelAction", queryString="csrfToken=#event.getCsrfToken()#&_wid=#UrlEncode( args.obfuscatedFields )##extraQs#" ) + args.cancelLink = args.cancelLink ?: event.buildLink( linkto="webflow.default.cancelAction", queryString=webflowActionQs ); args.cancelButton = renderViewlet( event="webflow.default.cancelButton", args=args ); } + if ( Len( additionalHandler ) && getController().handlerExists( additionalHandler ) ) { + additionalHandlerResult = runEvent( + event = additionalHandler + , private = true + , prePostExempt = true + , eventArguments = { args=args } + ); + + if ( IsStruct( additionalHandlerResult ) ) { + StructAppend( additionalAction, additionalHandlerResult, true ); + } + } + + args.additionalActionLabel = Trim( additionalAction.label ?: "" ); + args.additionalActionClass = Trim( additionalAction.class ?: "" ); + args.extraButtonGroupWith = LCase( Trim( additionalAction.groupWith ?: "next" ) ); + additionalActionUrlValue = Trim( additionalAction.url ?: "" ); + + if ( Len( additionalActionUrlValue ) ) { + if ( ReFindNoCase( "^(https?:|/|##|\?|//)", additionalActionUrlValue ) ) { + additionalActionHref = additionalActionUrlValue; + } else if ( getController().handlerExists( additionalActionUrlValue ) ) { + if ( event.isAdminRequest() ) { + additionalActionHref = event.buildAdminLink( linkto=additionalActionUrlValue, queryString=webflowActionQs ); + } else { + additionalActionHref = event.buildLink( linkto=additionalActionUrlValue, queryString=webflowActionQs ); + } + } + } + + args.additionalActionUrl = Trim( additionalActionHref ); + + if ( Len( args.additionalActionLabel ) && Len( args.additionalActionUrl ) ) { + args.extraButton = renderViewlet( event="webflow.default.extraButton", args=args ); + } + return renderView( view="/webflow/default/actions", args=args ); } diff --git a/system/i18n/webflow.properties b/system/i18n/webflow.properties index 5ef9207c99..dcc463bc02 100755 --- a/system/i18n/webflow.properties +++ b/system/i18n/webflow.properties @@ -2,6 +2,11 @@ next=Next back=Back cancel=Cancel +button.prev.class= +button.cancel.class= +button.extra.class= +button.next.class= + error.unexpected.submission.error=There was an unexpected error processing your submission. If the problem persists, please contact the website administrator. error.missing.submission.args=There was an unexpected error processing your submission. If the problem persists, please contact the website administrator. error.csrf.token.invalid=The page you submitted has expired and we have prevented processing of your submission as a security precaution. Please re-submit the page to continue. @@ -9,4 +14,4 @@ error.validation.errors=There were one or more problems with your submission. Pl error.inactive.timeout=Your progress has been reset due to a period of inactivity. Please try again. error.step.not.found=Your progress has been reset due to a period of inactivity or an unexpected internal error. We apologize for the inconvenience caused. Please try again. -ajaxLayout.missing.link.error=There was an unexpected error displaying your progress. If the problem persists, please contact the website administrator. \ No newline at end of file +ajaxLayout.missing.link.error=There was an unexpected error displaying your progress. If the problem persists, please contact the website administrator. diff --git a/system/views/webflow/default/actions.cfm b/system/views/webflow/default/actions.cfm index a226a5e5ff..ce2b7eb987 100755 --- a/system/views/webflow/default/actions.cfm +++ b/system/views/webflow/default/actions.cfm @@ -1,12 +1,20 @@ event.include( "/js/frontend/webflow/actions/" ); + + extraButtonGroupWith = args.extraButtonGroupWith ?: "next";
#( args.prevButton ?: "" )# + + #( args.extraButton ?: "" )# + #( args.cancelButton ?: "" )# + + #( args.extraButton ?: "" )# + #( args.nextButton ?: "" )#
-
\ No newline at end of file + diff --git a/system/views/webflow/default/cancelButton.cfm b/system/views/webflow/default/cancelButton.cfm index c228a77771..45eb844123 100755 --- a/system/views/webflow/default/cancelButton.cfm +++ b/system/views/webflow/default/cancelButton.cfm @@ -2,13 +2,18 @@ btnTitle = args.stepConfig.cancel_button ?: ""; cancelLink = args.cancelLink ?: ""; + btnClass = "webflow-cancel-btn"; if ( !Len( Trim( btnTitle ) ) ) { btnTitle = translateResource( "webflow:cancel" ); } + + if ( !event.isAdminRequest() ) { + btnClass &= " btn webflow-btn #Trim( translateResource( uri="webflow:button.cancel.class", defaultValue="" ) )#"; + } - \ No newline at end of file + diff --git a/system/views/webflow/default/extraButton.cfm b/system/views/webflow/default/extraButton.cfm new file mode 100644 index 0000000000..49ea8b2e51 --- /dev/null +++ b/system/views/webflow/default/extraButton.cfm @@ -0,0 +1,23 @@ + + + btnTitle = Trim( args.additionalActionLabel ?: "" ); + btnLink = Trim( args.additionalActionUrl ?: "" ); + btnClass = Trim( args.additionalActionClass ?: "" ); + groupWith = LCase( Trim( args.extraButtonGroupWith ?: "next" ) ); + containerClass = "webflow-extra-btn-container"; + + if ( groupWith == "prev" || groupWith == "standalone" ) { + containerClass &= " webflow-extra-btn-container-#groupWith#"; + } else { + containerClass &= " webflow-extra-btn-container-next"; + } + + if ( !Len( btnClass ) && !event.isAdminRequest() ) { + btnClass = Trim( translateResource( uri="webflow:button.extra.class", defaultValue="" ) ); + } + + + + diff --git a/system/views/webflow/default/nextButton.cfm b/system/views/webflow/default/nextButton.cfm index 45be044e35..cbe0c251a5 100755 --- a/system/views/webflow/default/nextButton.cfm +++ b/system/views/webflow/default/nextButton.cfm @@ -1,9 +1,16 @@ btnTitle = args.stepConfig.next_button ?: ""; + btnClass = "btn webflow-btn webflow-next-btn"; + + if ( !event.isAdminRequest() ) { + btnClass &= " #Trim( translateResource( uri="webflow:button.next.class", defaultValue="" ) )#"; + } else { + btnClass &= " btn-primary"; + }
- +
-
\ No newline at end of file + diff --git a/system/views/webflow/default/prevButton.cfm b/system/views/webflow/default/prevButton.cfm index d066f93562..3b918ff9c1 100755 --- a/system/views/webflow/default/prevButton.cfm +++ b/system/views/webflow/default/prevButton.cfm @@ -2,9 +2,14 @@ btnTitle = args.stepConfig.back_button ?: ""; prevLink = args.prevLink ?: ""; + btnClass = "btn webflow-btn webflow-prev-btn"; + + if ( !event.isAdminRequest() ) { + btnClass &= " #Trim( translateResource( uri="webflow:button.prev.class", defaultValue="" ) )#"; + } - \ No newline at end of file + From 52ab3ff03ecfaed031751e3aa8d2a319c32c69d5 Mon Sep 17 00:00:00 2001 From: Nick Cervantes Date: Fri, 17 Jul 2026 12:26:38 +0800 Subject: [PATCH 3/3] PRESIDECMS-3315: Add an option to declare a viewlet to each of the webflow actions. --- system/handlers/webflow/Default.cfc | 60 +++++++++++++++------ system/views/webflow/default/nextButton.cfm | 2 +- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/system/handlers/webflow/Default.cfc b/system/handlers/webflow/Default.cfc index 3077b1eebf..afbcccb410 100755 --- a/system/handlers/webflow/Default.cfc +++ b/system/handlers/webflow/Default.cfc @@ -219,14 +219,14 @@ component { } private string function actions( event, rc, prc, args={} ){ - var extraQs = ""; - var useAjaxLayout = IsTrue( args.useAjaxLayout ?: "" ); - var additionalAction = IsStruct( args.additionalAction ?: "" ) ? Duplicate( args.additionalAction ) : {}; - var additionalHandler = Trim( args.additionalActionHandler ?: "" ); - var additionalHandlerResult = ""; - var additionalActionHref = ""; - var additionalActionUrlValue = ""; - var webflowActionQs = ""; + var extraQs = ""; + var useAjaxLayout = IsTrue( args.useAjaxLayout ?: "" ); + var additionalAction = IsStruct( args.additionalAction ?: "" ) ? Duplicate( args.additionalAction ) : {}; + var additionalActionHandler = Trim( args.additionalActionHandler ?: "" ); + var additionalActionHandlerResult = ""; + var additionalActionHref = ""; + var additionalActionUrlValue = ""; + var webflowActionQs = ""; if ( useAjaxLayout ) { extraQs = "&args.useAjaxLayout=true&_rurl=#args.returnUrl ?: ""#"; @@ -235,27 +235,39 @@ component { webflowActionQs = "csrfToken=#event.getCsrfToken()#&_wid=#UrlEncode( args.obfuscatedFields )##extraQs#"; if ( IsTrue( args.hasNext ?: "" ) && IsFalse( args.disableNext ?: "" ) ) { - args.nextButton = renderViewlet( event="webflow.default.nextButton", args=args ); + args.nextButton = _renderActionButtonViewlet( + viewletEvent = args.nextButtonViewlet ?: "" + , defaultViewlet = "webflow.default.nextButton" + , args = args + ); } if ( IsTrue( args.hasPrev ?: "" ) && IsFalse( args.disablePrev ?: "" ) ) { args.prevLink = args.prevLink ?: event.buildLink( linkto="webflow.default.backAction", queryString=webflowActionQs ); - args.prevButton = renderViewlet( event="webflow.default.prevButton", args=args ); + args.prevButton = _renderActionButtonViewlet( + viewletEvent = args.prevButtonViewlet ?: "" + , defaultViewlet = "webflow.default.prevButton" + , args = args + ); } if ( IsTrue( args.canCancel ?: "" ) ) { args.cancelLink = args.cancelLink ?: event.buildLink( linkto="webflow.default.cancelAction", queryString=webflowActionQs ); - args.cancelButton = renderViewlet( event="webflow.default.cancelButton", args=args ); + args.cancelButton = _renderActionButtonViewlet( + viewletEvent = args.cancelButtonViewlet ?: "" + , defaultViewlet = "webflow.default.cancelButton" + , args = args + ); } - if ( Len( additionalHandler ) && getController().handlerExists( additionalHandler ) ) { - additionalHandlerResult = runEvent( - event = additionalHandler + if ( Len( additionalActionHandler ) && getController().handlerExists( additionalActionHandler ) ) { + additionalActionHandlerResult = runEvent( + event = additionalActionHandler , private = true , prePostExempt = true , eventArguments = { args=args } ); - if ( IsStruct( additionalHandlerResult ) ) { - StructAppend( additionalAction, additionalHandlerResult, true ); + if ( IsStruct( additionalActionHandlerResult ) ) { + StructAppend( additionalAction, additionalActionHandlerResult, true ); } } @@ -279,7 +291,11 @@ component { args.additionalActionUrl = Trim( additionalActionHref ); if ( Len( args.additionalActionLabel ) && Len( args.additionalActionUrl ) ) { - args.extraButton = renderViewlet( event="webflow.default.extraButton", args=args ); + args.extraButton = _renderActionButtonViewlet( + viewletEvent = args.extraButtonViewlet ?: "" + , defaultViewlet = "webflow.default.extraButton" + , args = args + ); } return renderView( view="/webflow/default/actions", args=args ); @@ -547,4 +563,14 @@ component { } return event.buildLink( linkto="webflow.default.submitAction" ); } + + private string function _renderActionButtonViewlet( + required string viewletEvent + , required string defaultViewlet + , required struct args + ) { + var eventPath = Len( Trim( arguments.viewletEvent ) ) ? arguments.viewletEvent : arguments.defaultViewlet; + + return renderViewlet( event=eventPath, args=arguments.args ); + } } \ No newline at end of file diff --git a/system/views/webflow/default/nextButton.cfm b/system/views/webflow/default/nextButton.cfm index cbe0c251a5..1bd6d2f495 100755 --- a/system/views/webflow/default/nextButton.cfm +++ b/system/views/webflow/default/nextButton.cfm @@ -11,6 +11,6 @@
- +