我的表单有一个项目列表,每个项目都有一个删除按钮。我需要提交要删除的项目的索引以及表单的其他值(以便进一步编辑)。
使用JavaScript,它看起来像这样:
<g:form method="post" mapping="defaultAction" id="${paymentInstance?.id}">
<g:hiddenField name="id" value="${paymentInstance?.id}"/>
<g:hiddenField name="version" value="${paymentInstance?.version}"/>
<g:hiddenField name="deleteAdjIdx"/>
<g:each in="${paymentInstance?.adjustments}" var="adj" status="idx">
<g:set var="adjName" value="adjustments[${idx}]"/>
<g:textField name="${adjName}.dollars" value="${adj.dollars}"/>
<g:actionSubmit action="deleteAdj" value="delete" onclick="jQuery('#deleteAdjIdx').val(${idx})"/>
</g:each>
</g:form>
如果没有JavaScript,我怎么能这样做? (除了发布的params之外,我可以在URL中添加一个参数吗?或者,映射中的某种多路复用?)
defaultAction映射是:
name defaultAction: "/$controller/$id"{ // stable URL for payments regardless of current status (editable or not)
constraints {
id(matches: /\d+/) // since our action names don't start with a digit but many domain ids do
}
}
答案 0 :(得分:1)
我遇到了同样的问题,并找到了使用HTML5的“格式”属性的另一种解决方案。 它们可以被赋予一个值,包括控制器,动作,附加参数等。
通常,在表单上向提交按钮添加参数(例如删除特定的子对象)将如下所示:
<input type="submit" formaction="/<controller>/<action>/<id>?additionalParam1=...&additionalParam2=..." value="Delete" >
并在您的示例中:
<input type="submit" formaction="/payment/deleteAdj/${adj.id}" value="delete" >
或
<input type="submit" formaction="/payment/deleteAdj?delAdjID=${adj.id}" value="delete" >
在deleteAdj中你会删除params.id或params.delAdjID中所述的调整并保存 params中的数据或直接使用params重新填充表单(例如,在渲染create.gsp时)。
答案 1 :(得分:0)
您的代码似乎创建了多个删除按钮,每个按钮点击重新加载页面。
那么为什么不创建几种形式:
<g:each in="${paymentInstance?.adjustments}" var="adj" status="idx">
<g:form method="post" mapping="defaultAction" id="${paymentInstance?.id}">
<g:hiddenField name="id" value="${paymentInstance?.id}"/>
<g:hiddenField name="version" value="${paymentInstance?.version}"/>
<g:hiddenField name="deleteAdjIdx" value="${idx}"/>
<g:set var="adjName" value="adjustments[${idx}]"/>
<g:textField name="${adjName}.dollars" value="${adj.dollars}"/>
<g:actionSubmit action="deleteAdj" value="delete" />
</g:form>
</g:each>
<强>更新强>:
如果您需要一个表单,您还可以使用常规提交按钮:为表单提供固定操作,并将提交按钮设为值:
<g:form method="post"
controller="payment" action="myAction"
id="${paymentInstance?.id}">
<g:each in="${paymentInstance?.adjustments}" var="adj" status="idx">
<g:hiddenField name="id" value="${paymentInstance?.id}"/>
<g:hiddenField name="version" value="${paymentInstance?.version}"/>
<g:set var="adjName" value="adjustments[${idx}]"/>
<g:textField name="${adjName}.dollars" value="${adj.dollars}"/>
<input type="submit" name="delete" value="${idx}"/>
</g:each>
</g:form>
这应该会产生一个由payment
控制器和myAction
处理的表单。在此操作中,您可以检查params.delete
是否存在。此参数的值将是${idx}
的值。
如果您需要此表单的其他操作,您只需以相同的方式添加其他提交按钮,并检查其params-value是否存在。然后,您可以跳转到控制器中的另一个操作(这就是在后台处理这些<g:actionSubmit .../>
按钮的方式。
更新2: 你是绝对正确的:按钮上显示的索引是丑陋的。另一种方法是使用我第一次更新的代码并将按钮更改为
<input type="submit" name="delete_${idx}" value="Delete me!"/>
在你的控制器中,你必须使用这样的代码
def idx = null
params.each { key, value ->
if (key.startsWith('delete_')) {
idx = key-"delete_"
}
}
if (idx) {
// delete element
}
或者如果你喜欢一个更酷的版本
def idx = (params.find { key, value -> key.startsWith('delete_')})?.key
if (idx) {
id -= 'delete_'
// delete element
}
甚至更混淆: - )
def idx = ''+(params.find { key, value -> key.startsWith('delete_')})?.key-'null'-'delete_'
if (idx) {
// delete element
}
答案 2 :(得分:0)
您只能使用一个提交按钮,并将g:each
替换为radioGroup
示例:
<g:radioGroup name="adj" labels="[put here your text]" values="[put here the indexes]">
<p>${it.label} ${it.radio}</p>
</g:radioGroup>
现在adj
将保留要删除的项目的值
答案 3 :(得分:0)
我将尝试在actionSubmit中添加params到动作名称:
<my:actionSubmit action="deleteAdj" value="delete" params="${[deleteAdjIdx: idx]})"/>
我没有找到一个很好的方法来做到这一点,所以我提出了以下黑客攻击。如果有人知道任何改进,我会很感激。
添加到MyTagLib:
import org.codehaus.groovy.grails.web.util.WebUtils
def actionSubmit = { attrs ->
String action = attrs.action ?: attrs.value
Map params = attrs.remove('params')
if (params) {
attrs.action = (action + WebUtils.toQueryString(params)).encodeAsURL()
}
out << g.actionSubmit(attrs)
}
我解压缩参数并在过滤器中恢复原始操作名称:
package com.example;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.web.filter.GenericFilterBean
import org.codehaus.groovy.grails.web.util.WebUtils
import com.example.util.MyWebUtils
import javax.servlet.http.HttpServletRequest;
/**
* Handles params packed into the MyTagLib.actionSubmit param name.
* This Filter needs to come before GrailsWebRequestFilter.
* <p/>
* Updating the GrailsParameterMap after the GrailsWebRequestFilter doesn't work,
* because the request given to DefaultUrlMappingInfo.checkDispatchAction()
* is the immutable request saved in the GrailsWebRequest, not the wrapped request
* that this filter passes on down the chain.
*/
public class ActionSubmitParamFilter extends GenericFilterBean {
final static QUERY_SEPARATOR = '?'
final static QUERY_SEPARATOR_REGEX = '[?]'
final static PARAM_SEPARATOR_REGEX = '[&]'
@Override
void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
def dispatchAction = request.parameterNames.find {it.startsWith(WebUtils.DISPATCH_ACTION_PARAMETER)}
def decoded = dispatchAction?.decodeURL()
if (decoded?.contains(QUERY_SEPARATOR)) {
def overrides = unpackParams(decoded)
overrides[dispatchAction] = null // deletes from the overridden request params
request = MyWebUtils.overrideParams((HttpServletRequest) request, overrides)
}
chain.doFilter(request, response);
}
private Map unpackParams(String decoded) {
String action, paramsPart
(action, paramsPart) = decoded.split(QUERY_SEPARATOR_REGEX)
assert action.startsWith(WebUtils.DISPATCH_ACTION_PARAMETER)
def packedParams = [:].withDefault {[]}
packedParams[action] << ''
for (param in paramsPart.split(PARAM_SEPARATOR_REGEX)) {
if (param.contains('=')) {
def (name, value) = param.split('=')
packedParams[name.decodeURL()] << value.decodeURL()
} else {
packedParams[param.decodeURL()] << ''
}
}
packedParams
}
}
我使用实用程序方法来覆盖请求参数并配置过滤器:
package com.example.util
import org.springframework.web.multipart.MultipartHttpServletRequest
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletRequestWrapper
import groovy.util.slurpersupport.GPathResult
import com.example.ActionSubmitParamFilter
class MyWebUtils {
static HttpServletRequest overrideParams(HttpServletRequest original, Map<String, List<String>> overrides) {
assert !(original instanceof MultipartHttpServletRequest) // if we get one of these, we'll need to use a special wrapper
def params = (Map<String, String[]>) new HashMap(original.getParameterMap())
overrides.each {k, v ->
if (v == null) {
params.remove(k) // significant for parameterNames enumeration and parameterMap
} else {
params[k] = v as String[]
}
}
params = Collections.unmodifiableMap(params)
new HttpServletRequestWrapper(original) {
@Override
String[] getParameterValues(String name) {
params[name]
}
@Override
String getParameter(String name) {
String[] values = params[name]
values ? values[0] : (values == null ? null : '')
}
@Override
Map getParameterMap() {
params
}
@Override
Enumeration getParameterNames() {
Collections.enumeration(params.keySet())
}
}
}
// used dynamically by _Events.eventWebXmlStart
static void prependActionSubmitParamFilter(GPathResult webXml) {
def filters = webXml.filter
def filterMappings = webXml.'filter-mapping'
def firstFilter = filters[0]
def firstFilterMapping = filterMappings[0]
firstFilter + {
filter {
'filter-name'('actionSubmitParam')
'filter-class'(ActionSubmitParamFilter.name)
}
}
firstFilterMapping + {
'filter-mapping' {
'filter-name'('actionSubmitParam')
'url-pattern'("/*")
'dispatcher'("FORWARD")
'dispatcher'("REQUEST")
}
}
}
}
最后,我在_Events.groovy中添加了以下内容,通过扩展ControllersGrailsPlugin.doWithWebDescriptor在web.xml中配置过滤器:
eventWebXmlStart = {
pluginManager.getGrailsPlugin('controllers').with {
def originalClosure = instance.doWithWebDescriptor // extending
instance.doWithWebDescriptor = { GPathResult webXml ->
// static import fails after clean (before compile), so load dynamically
def webUtils = getClass().classLoader.loadClass('com.example.util.MyWebUtils')
webUtils.prependActionSubmitParamFilter(webXml)
// call super after (so above filter comes before GrailsWebRequestFilter in chain)
originalClosure.resolveStrategy = Closure.DELEGATE_FIRST
originalClosure.delegate = delegate
originalClosure(webXml)
}
}
}