用户需要保存他对 A 的更改,否则一切都将被丢弃
在我看来,您在不需要时急切地创建B
's - 您只想在用户通过保存确认整个操作时创建它们A
。
一个页面,他可以在其中编辑 A 的 b,他可以添加新的 b
看起来您也有一个页面,其中所有B
s 都显示以供编辑,因此没有真正需要在整个地方保留隐藏字段。
然后我要做的是保留视图中的所有当前更改,使用正常的表单输入,并调用单个事务操作,根据参数保存A
和创建/修改/删除B
's。
根据您的应用程序的外观,您可以通过多种方式执行此操作。
我过去使用的一个是有一个模板(比如说editB
),它接收 a B
、一个索引和 aprefix
并显示给定B
名称的相应输入,并以前缀为前缀${property}.
(即它B
在编辑模式下呈现给定)。
然后,编辑视图A
将呈现它拥有editB
的所有B
',并且:
- 添加一个新的
B
将触发一个 Ajax 调用,以检索该模板以获取新的 B、前缀b
(A
属性的名称)和对应于列表长度的索引。
- 删除一个
B
将简单地删除与模板相对应的 HTML 片段,并重新计算索引。
然后,在保存时A
,控制器将检查其中的内容params.list('b')
并相应地创建、更新和删除。
一般来说,它会是这样的:
模板/templates/_editB.gsp
<g:if test="${instance.id}">
<input type="hidden" name="${prefix}.id" value="${instance.id}" />
</g:if>
<g:else>
<input type="hidden" name="${prefix}.domainClassName" value=${instance.domainClass.clazz.name}" />
</g:else>
<input type="hidden" name="${prefix}.index" value=${instance.index}" />
<input type="..." name="${prefix}.info" value="${instance.info}" />
A 的编辑视图
<g:each var="b" in="${a.b.sort { it.index }}">
<g:render template="/templates/editB" model="${[instance: b, prefix: 'b']}" />
<button onClick="deleteTheBJustUpThereAndTriggerIndexRecalculation()">Remove</button>
</g:each>
<button onClick="addNewBByInvokingAController#renderNewB(calculateMaxIndex())">Remove</button>
控制器:
class AController {
private B getBInstance(String domainClassName, Map params) {
grailsApplication
.getDomainClass(domainClassName)
.clazz.newInstance(params)
}
def renderNewB(Integer index, String domainClassName) {
render template: '/templates/editB', model: [
instance: getBInstance(domainClassName, [index: index]),
prefix: 'b'
]
}
def save(Long id) {
A a = a.get(id)
bindData(a, params, [exclude: ['b']]) // We manually bind b
List bsToBind = params.list('b')
List<B> removedBs = a.b.findAll { !(it.id in bsToBind*.id) }
List newBsToBind = bsToBind.findAll { !it.id }
A.withTransaction { // Or move it to service
removedBs.each { // Remove the B's not present in params
a.removeFromB(it)
it.delete()
}
bsToBind.each { bParams ->
if (bParams.id) { // Just bind data for already existing B's
B b = a.b.find { it.id == bParams.id }
bindData(b, bParams, [exclude: 'id', 'domainClassName'])
}
else { // New B's are also added to a
B newB = getBInstance(bParams.remove('domainClassName'), bParams)
a.addToB(b)
}
}
a.save(failOnError:true)
}
}
}
renderNewB
缺少用于调用、删除现有B
's 的 HTML 片段和处理索引的 Javascript 函数,但我希望这个想法很清楚:)。
更新
我假设:
- 依赖会话获取关键信息并不是很好:会话可能会失效(例如,用户注销)并且它们不适合扩展。
- 为了不必在视图中携带对象而保存对象是一个糟糕而脆弱的想法 - 它很容易中断(会话无效,用户关闭浏览器,有一个部署并且会话没有持久化)并且需要清理。可以做到,但在我看来成本太高了。
我认为这需要一个更好的客户端,而不是依赖服务器技巧。您描述的更改并没有太大的不同。
- 将索引作为 的属性
B
使思考比处理SortedSet
/更容易List
:
- 显示时
A
,a.b.sort { it.index }
需要添加以保持顺序。
- 渲染时
B
,需要添加索引的隐藏输入。
- 当拖放或删除发生时,需要重新计算索引的 Javascript 函数。
- 绑定数据时,没有任何变化,因为索引只是一个属性。
- 继承
B
确实需要将域类作为视图中的隐藏输入(或使用一些 Javascript 来跟踪该信息,但我没有看到好处)。我不明白为什么这么糟糕。您正在使用继承作为“B 类型”。如果你有一个属性而不是继承B
,type
那么你会使用它的输入,对吗?
- 渲染新的时,需要传递
B
“类型”( )domainClassName
- 渲染时
B
,如果没有id
,则需要传递类名的隐藏输入
- 保存时
A
,新B
的是使用特定的域类创建的,否则没有任何变化。
我已更新代码以反映此更改。
如果我真的想预先保存对象怎么办?
如果您真的确信这是正确的方法,我仍然会尝试避免会话并为B
被调用添加一个新属性confirmed
。
- 当用户添加一个新的
B
,confirmed 设置为 false。
- 当用户保存一个
A
时,所有B
没有被删除的所有物都被confirmed
设置为true
,被删除的很好,被删除了:)。
- 显示时
A
,仅显示已确认B
的 。
即使用户关闭浏览器或会话失效,未确认的也永远不会显示给用户,并且在再次保存B
时最终会被删除。A
您还可以添加一个 Quartz 作业,B
根据一些超时定期清理未确认的 s,但这很棘手 - 因为保存未确认数据的整个想法是:-)。