Skip to content

Commit

Permalink
feature #551 - replace actionSubmit with formActionSubmit (#552)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdaugherty authored Dec 1, 2024
1 parent 416e832 commit 151a321
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,20 @@ class ApplicationTagLib implements ApplicationContextAware, InitializingBean, Gr
* &lt;g:link action="myaction"&gt;link 1&lt;/gr:link&gt;<br/>
* &lt;g:link controller="myctrl" action="myaction"&gt;link 2&lt;/gr:link&gt;<br/>
*
* @attr controller The name of the controller to use in the link, if not specified the current controller will be linked
* @attr action The name of the action to use in the link, if not specified the default action will be linked
* @attr uri relative URI
* @attr url A map containing the action,controller,id etc.
* @attr base Sets the prefix to be added to the link target address, typically an absolute server URL. This overrides the behaviour of the absolute property, if both are specified.
* @attr absolute If set to "true" will prefix the link target address with the value of the grails.serverURL property from Config, or http://localhost:&lt;port&gt; if no value in Config and not running in production.
* @attr controller The name of the controller to use in the link, if not specified the current controller will be linked
* @attr namespace The namespace of the controller to use in the link
* @attr plugin The name of the plugin which provides the controller
* @attr id The id to use in the link
* @attr fragment The link fragment (often called anchor tag) to use
* @attr params A map containing URL query parameters
* @attr mapping The named URL mapping to use to rewrite the link
* @attr method The HTTP method specified in the corresponding URL mapping
* @attr params A map containing URL query parameters for the link
* @attr url A map containing the action, controller, id etc.
* @attr uri A string for a relative path in the running app.
* @attr relativeUri Used to specify a uri relative to the current path.
* @attr absolute If set to "true" will prefix the link target address with the value of the grails.serverURL property from Config, or http://localhost:&lt;port&gt; if no value in Config and not running in production.
* @attr base Sets the prefix to be added to the link target address, typically an absolute server URL. This overrides the behaviour of the absolute property, if both are specified.
* @attr event Webflow _eventId parameter
* @attr elementId DOM element id
*/
Expand Down Expand Up @@ -345,7 +349,7 @@ class ApplicationTagLib implements ApplicationContextAware, InitializingBean, Gr
* @attr controller The name of the controller to use in the link, if not specified the current controller will be linked
* @attr action The name of the action to use in the link, if not specified the default action will be linked
* @attr uri relative URI
* @attr url A map containing the action,controller,id etc.
* @attr url A map containing the action, controller, id etc.
* @attr base Sets the prefix to be added to the link target address, typically an absolute server URL. This overrides the behaviour of the absolute property, if both are specified.
* @attr absolute If set to "true" will prefix the link target address with the value of the grails.serverURL property from Config, or http://localhost:&lt;port&gt; if no value in Config and not running in production.
* @attr id The id to use in the link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import grails.artefact.TagLibrary
import grails.config.Config
import grails.core.support.GrailsConfigurationAware
import grails.gsp.TagLib
import grails.web.mapping.LinkGenerator
import groovy.transform.CompileStatic
import groovy.util.logging.Commons
import org.grails.plugins.web.GrailsTagDateHelper
Expand All @@ -30,7 +31,6 @@ import java.text.DateFormatSymbols
import org.grails.core.artefact.DomainClassArtefactHandler
import org.grails.encoder.CodecLookup
import org.grails.encoder.Encoder
import grails.web.mapping.LinkGenerator
import org.grails.buffer.FastStringWriter
import org.grails.web.servlet.mvc.SynchronizerTokensHolder
import org.grails.buffer.GrailsPrintWriter
Expand Down Expand Up @@ -384,7 +384,7 @@ class FormTagLib implements ApplicationContextAware, InitializingBean, TagLibrar
* @attr action the name of the action to use in the link, if not specified the default action will be linked
* @attr controller the name of the controller to use in the link, if not specified the current controller will be linked
* @attr id The id to use in the link
* @attr url A map containing the action,controller,id etc.
* @attr url A map containing the action, controller, id etc.
* @attr name A value to use for both the name and id attribute of the form tag
* @attr useToken Set whether to send a token in the request to handle duplicate form submissions. See Handling Duplicate Form Submissions
* @attr method the form method to use, either 'POST' or 'GET'; defaults to 'POST'
Expand All @@ -403,7 +403,7 @@ class FormTagLib implements ApplicationContextAware, InitializingBean, TagLibrar
* @attr action the name of the action to use in the link, if not specified the default action will be linked
* @attr controller the name of the controller to use in the link, if not specified the current controller will be linked
* @attr id The id to use in the link
* @attr url A map containing the action,controller,id etc.
* @attr url A map containing the action, controller, id etc.
* @attr name A value to use for both the name and id attribute of the form tag
* @attr useToken Set whether to send a token in the request to handle duplicate form submissions. See Handling Duplicate Form Submissions
* @attr method the form method to use, either 'POST' or 'GET'; defaults to 'POST'
Expand All @@ -420,16 +420,16 @@ class FormTagLib implements ApplicationContextAware, InitializingBean, TagLibrar

def linkAttrs = attrs.subMap(LinkGenerator.LINK_ATTRIBUTES)

writer << "<form action=\""
writer << '<form action="'

// Call RequestDataValueProcessor to modify url if necessary
def link = createLink(linkAttrs)
if (requestDataValueProcessor != null) {
link= requestDataValueProcessor.processAction(request, link, request.method)
link = requestDataValueProcessor.processAction(request, link, request.method)
}

writer << link
writer << "\" "
writer << '" '

// if URL is not null remove attributes
if (attrs.url == null) {
Expand Down Expand Up @@ -518,10 +518,13 @@ class FormTagLib implements ApplicationContextAware, InitializingBean, TagLibrar
* @attr value REQUIRED The title of the button and name of action when not explicitly defined.
* @attr action The name of the action to be executed, otherwise it is derived from the value.
* @attr disabled Makes the button to be disabled. Will be interpreted as a Groovy Truth
* @deprecated As of 7.0.0, use {@link #formActionSubmit} instead
*
*/
@Deprecated(since = '7.0.0')
Closure actionSubmit = { attrs ->
if (!attrs.value) {
throwTagError("Tag [actionSubmit] is missing required attribute [value]")
throwTagError('Tag [actionSubmit] is missing required attribute [value]')
}

attrs.tagName = "actionSubmit"
Expand All @@ -548,6 +551,70 @@ class FormTagLib implements ApplicationContextAware, InitializingBean, TagLibrar
out << '/>'
}

/**
* Creates a submit button using the `formaction` attribute to submit to a different action than the form.
* The action will be generated by the various link attributes.<br/>
*
* &lt;g:formActionSubmit action="myaction" value="Submit"/&gt;<br/>
* &lt;g:formActionSubmit controller="myctrl" action="myaction" value="ButtonName"/&gt;<br/>
*
* @attr id the id attribute of the formActionSubmit tag
* @attr value the button's show value
* @attr action The name of the action to use in the link, if not specified the default action will be linked
* @attr controller The name of the controller to use in the link, if not specified the current controller will be linked
* @attr namespace The namespace of the controller to use in the link
* @attr plugin The name of the plugin which provides the controller
* @attr id The id to use in the link
* @attr fragment The link fragment (often called anchor tag) to use
* @attr mapping The named URL mapping to use to rewrite the link
* @attr method The HTTP method specified in the corresponding URL mapping
* @attr params A map containing URL query parameters for the link
* @attr url A map containing the action, controller, id etc.
* @attr uri A string for a relative path in the running app.
* @attr relativeUri Used to specify a uri relative to the current path.
* @attr absolute If set to "true" will prefix the link target address with the value of the grails.serverURL property from Config, or http://localhost:&lt;port&gt; if no value in Config and not running in production.
* @attr base Sets the prefix to be added to the link target address, typically an absolute server URL. This overrides the behaviour of the absolute property, if both are specified.
* @attr event Webflow _eventId parameter
*/
def formActionSubmit = { Map attrs ->
if (!attrs.value) {
throwTagError('Tag [formActionSubmit] is missing required attribute [value]')
}

def elementId = attrs.remove('id')

// the following attributes are reserved because this tag must be of type `submit` and the `formaction` attr
// will be generated by the link attributes.
attrs.remove('type')
attrs.remove('formAction')

This comment has been minimized.

Copy link
@jeffscottbrown

jeffscottbrown Dec 2, 2024

Member

If the formAction attribute were specified, do you think a warning should be logged letting the developer know that it is being ignored?

This comment has been minimized.

Copy link
@jdaugherty

jdaugherty Dec 3, 2024

Author Contributor

The other tags silently replace attributes if the tag definition depends on it. For example textField doesn't let you specify the type. It's also not a documented attribute and the source is available. I'm ok adding this if you feel strongly.

This comment has been minimized.

Copy link
@jeffscottbrown

jeffscottbrown Dec 3, 2024

Member

"I'm ok adding this if you feel strongly." - I don't feel strongly. You make a good point that there is precedent.


out << '<input type="submit" formaction="'

Map linkAttrs = attrs.subMap(LinkGenerator.LINK_ATTRIBUTES - 'elementId')

// Call RequestDataValueProcessor to modify url if necessary
String link = createLink(linkAttrs)
if (requestDataValueProcessor != null) {
link = requestDataValueProcessor.processAction(request, link, request.method)
}

out << link
out << '" '

attrs.keySet().removeAll(LinkGenerator.LINK_ATTRIBUTES)
if (elementId) {
attrs['id'] = elementId
}

booleanToAttribute(attrs, 'disabled')

// process remaining attributes
outputAttributes(attrs, out, false)

// close tag
out << '/>'
}

/**
* Creates a an image submit button that submits to an action in the controller specified by the form action.
* The name of the action attribute is translated into the action name, for example "Edit" becomes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,12 @@ class FormTagLibTests extends Specification implements TagLibUnitTest<FormTagLib
def testFormWithURL() {
when:
unRegisterRequestDataValueProcessor()
String output = tagLib.form(new TreeMap([url:[controller:'con', action:'action'], id:'formElementId']))
String output = tagLib.form(
new TreeMap([
url: [controller: 'con', action: 'action'],
id: 'formElementId'
])
)

then:
output == '<form action="/con/action" method="post" id="formElementId" ></form>'
Expand All @@ -294,12 +299,85 @@ class FormTagLibTests extends Specification implements TagLibUnitTest<FormTagLib
def testFormWithURLAndRequestDataValueProcessor() {

when:
String output = tagLib.form(new TreeMap([url:[controller:'con', action:'action'], id:'formElementId']))
String output = tagLib.form(
new TreeMap([
url: [controller: 'con', action: 'action'],
id: 'formElementId'
])
)

then:
output == '<form action="/con/action" method="post" id="formElementId" ><input type="hidden" name="requestDataValueProcessorHiddenName" value="hiddenValue" />\n</form>'
}

def testFormActionSubmitWithController() {
when:
String output = tagLib.formActionSubmit([controller: 'con', id: 'formElementId', value: 'Submit'])

then:
output == '<input type="submit" formaction="/con" value="Submit" id="formElementId" />'
}

def testFormActionSubmitWithControllerAndAction() {
when:
String output = tagLib.formActionSubmit([controller: 'con', action: 'act', id: 'formElementId', value: 'Submit'])

then:
output == '<input type="submit" formaction="/con/act" value="Submit" id="formElementId" />'
}

def testFormActionSubmitWithURLAndNoParams() {
when:
unRegisterRequestDataValueProcessor()
String output = tagLib.formActionSubmit(new TreeMap([url: [controller: 'con', action:'action'], id: 'formElementId', value: 'Submit']))

then:
output == '<input type="submit" formaction="/con/action" id="formElementId" value="Submit" />'
}

def testFormActionSubmitWithAURLAndRequestDataValueProcessor() {
when:
String output = tagLib.formActionSubmit(
new TreeMap([
url: [
controller: 'con',
action:'action',
params: [
requestDataValueProcessorParamName: 'paramValue'
]
],
id: 'formElementId',
value: 'My Button'
])
)

then:
output == '<input type="submit" formaction="/con/action" id="formElementId" value="My Button" />'
}

def testFormActionSubmitWithAURLAndWithoutRequestDataValueProcessor() {
given:
unRegisterRequestDataValueProcessor()

when:
String output = tagLib.formActionSubmit(
new TreeMap([
url: [
controller: 'con',
action:'action',
params: [
requestDataValueProcessorParamName: 'paramValue'
]
],
id: 'formElementId',
value: 'My Button'
])
)

then:
output == '<input type="submit" formaction="/con/action?requestDataValueProcessorParamName=paramValue" id="formElementId" value="My Button" />'
}

def testActionSubmitWithoutAction() {

when:
Expand Down
4 changes: 2 additions & 2 deletions src/main/docs/guide/tags/formsAndFields.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ GSP also contains extended helper versions of the above tags such as xref:../ref
==== Multiple Submit Buttons


The age-old problem of dealing with multiple submit buttons is also handled elegantly with Grails using the xref:../ref/Tags/actionSubmit.adoc[actionSubmit] tag. It is just like a regular submit, but lets you specify an alternative action to submit to:
The age-old problem of dealing with multiple submit buttons is also handled elegantly with Grails using the xref:../ref/Tags/formActionSubmit.adoc[formActionSubmit] tag. It is just like a regular submit, but lets you specify an alternative controller & action to submit to:

[source,xml]
----
<g:actionSubmit value="Some update label" action="update" />
<g:formActionSubmit value="Some update label" controller="mycontroller" action="update" />
----
2 changes: 2 additions & 0 deletions src/main/docs/ref/Tags/actionSubmit.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

Generates a submit button that maps to a specific action, which lets you have multiple submit buttons in a single form. JavaScript event handlers can be added using the same parameter names as in HTML.

_Note: this tag is being replaced by the tag formActionSubmit_


=== Examples

Expand Down
8 changes: 4 additions & 4 deletions src/main/docs/ref/Tags/createLink.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ Attributes

* `action` (optional) - The name of the action to use in the link; if not specified the default action will be linked
* `controller` (optional) - The name of the controller to use in the link; if not specified the current controller will be linked
* `namespace` (optional) - the namespace of the controller to use in the link
* `plugin` (optional) - the name of the plugin which provides the controller
* `namespace` (optional) - The namespace of the controller to use in the link
* `plugin` (optional) - The name of the plugin which provides the controller
* `id` (optional) - The id to use in the link
* `fragment` (optional) - The link fragment (often called anchor tag) to use
* `mapping` (optional) - The {grailsdocs}guide/theWebLayer.html#namedMappings[named URL mapping] to use to rewrite the link
* `method` (optional) - The HTTP method specified in the corresponding URL mapping
* `params` (optional) - A Map of request parameters
* `url` (optional) - A Map containing the action,controller,id etc.
* `params` (optional) - A map containing URL query parameters for the link
* `url` (optional) - A Map containing the action, controller, id etc.
* `uri` (optional) - A string for a relative path in the running app.
* `relativeUri` (optional) - Used to specify a uri relative to the current path.
* `absolute` (optional) - If `true` will prefix the link target address with the value of the `grails.serverURL` property from the application configuration, or http://localhost:<port> if there is no setting in the config and not running in production.
Expand Down
2 changes: 1 addition & 1 deletion src/main/docs/ref/Tags/form.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Attributes
* `fragment` (optional) - The link fragment (often called anchor tag) to use
* `mapping` (optional) - The {grailsdocs}guide/theWebLayer.html#namedMappings[named URL mapping] to use to rewrite the link
* `params` (optional) - A Map of request parameters
* `url` (optional) - A map containing the action,controller,id etc.
* `url` (optional) - A map containing the action, controller, id etc.
* `relativeUri` (optional) - Used to specify a uri relative to the current path.
* `absolute` (optional) - If `true` will prefix the link target address with the value of the `grails.serverURL` property from the application configuration, or http://localhost:<port> if there is no setting in the config and not running in production.
* `base` (optional) - Sets the prefix to be added to the link target address, typically an absolute server URL. This overrides the behaviour of the `absolute` property, if both are specified.
Expand Down
Loading

0 comments on commit 151a321

Please sign in to comment.