Skip to content

Commit

Permalink
feature #551 - replace actionSubmit with formActionSubmit
Browse files Browse the repository at this point in the history
  • Loading branch information
jdaugherty committed Nov 29, 2024
1 parent 416e832 commit 238b097
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 3 deletions.
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 @@ -519,6 +519,7 @@ class FormTagLib implements ApplicationContextAware, InitializingBean, TagLibrar
* @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
Closure actionSubmit = { attrs ->
if (!attrs.value) {
throwTagError("Tag [actionSubmit] is missing required attribute [value]")
Expand Down Expand Up @@ -548,6 +549,57 @@ class FormTagLib implements ApplicationContextAware, InitializingBean, TagLibrar
out << '/>'
}

/**
* Creates a submit button that submits to an action 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 action the name of the action to use in the submit action, if not specified the default action will be linked
* @attr controller the name of the controller to use in the submit action, if not specified the current controller will be linked
* @attr id the id attribute of the formActionSubmit tag
* @attr url A map containing the action,controller,id etc.
* @attr value the button's show value
*/
def formActionSubmit = { Map attrs ->
if (!attrs.value) {
throwTagError("Tag [formActionSubmit] is missing required attribute [value]")
}

def elementId = attrs.remove('id')

// Reserved
attrs.remove('type')
attrs.remove('formAction')
attrs.remove('method') // specified by the controller

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 @@ -300,6 +300,51 @@ class FormTagLibTests extends Specification implements TagLibUnitTest<FormTagLib
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
59 changes: 59 additions & 0 deletions src/main/docs/ref/Tags/formActionSubmit.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

== formActionSubmit



=== Purpose


Creates a submit button that submits to an `formaction` generated by the various link attributes.


=== Examples


[,xml]
----
<!--Generates a label of 'Update' submitting to `update` action on the current controller-->
<g:formActionSubmit value="Update" />
<!--'Update' is action, label is 'Some update label'-->
<g:formActionSubmit value="Some update label" action="update" />
<!--'Update' is action on the controller 'index', label is 'Some update label'-->
<g:formActionSubmit value="Some update label" controller="index" action="update" />
<!--label derived from message bundle-->
<g:formActionSubmit value="${message(code:'label.update')}" action="update" />
<g:formActionSubmit value="Delete" />
<g:formActionSubmit value="DeleteAll"
onclick="return confirm('Are you sure???')" />
----


=== Description


Attributes

* `action` (optional) - The name of the action to use in the formaction; if not specified the default action will be used
* `controller` (optional) - The name of the controller to use in the formaction; if not specified the current controller will be used
* `namespace` (optional) - the namespace of the controller to use in the formaction
* `plugin` (optional) - the name of the plugin which provides the controller
* `id` (optional) - The element id of the input element
* `fragment` (optional) - The link fragment (often called anchor tag) to use in the formaction
* `mapping` (optional) - The {grailsdocs}guide/theWebLayer.html#namedMappings[named URL mapping] to use to rewrite the formaction
* `params` (optional) - A Map of request parameters for the formaction
* `url` (optional) - A Map containing the action,controller,id etc for the for the formaction.
* `uri` (optional) - A string for a relative path in the running app
* `relativeUri` (optional) - Used to specify a uri relative to the current path for the formaction
* `absolute` (optional) - If `true` will prefix the formaction 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 formaction target address, typically an absolute server URL. This overrides the behaviour of the `absolute` property if both are specified.
* `event` (optional) - The name of a Webflow event to trigger for the flow associated with the given `action`. Requires the Webflow plugin.
* `value` (required) - The caption of the button and name of action when not explicitly defined.

When you use a normal submit button inside a form, it is the form itself that determines what URL the request is sent to, and therefore what action is executed. However, this tag overrides that behaviour and determines which action is executed by the specified attributes. This tag supports any controller & action.

0 comments on commit 238b097

Please sign in to comment.