处理grails中未保存的对象状态

时间:2015-07-28 13:31:42

标签: javascript ajax grails window.onunload

我遇到的问题可能是设计问题,而不是技术问题。

假设我有这些课程:

class A {
    def someInfo
    static hasMany = [b: B]
}

class B {
    A a
    def info
    def moreInfo
    ...
    def aLotMoreInfo
}

现在让我们假设用户在一个页面上,他可以编辑A的b,他可以添加新的b。但是用户需要将他的更改保存到A,否则一切都将被丢弃。

我目前的方法是创建额外的b,通过AJAX渲染它们并将它们的ID保存在会话变量中,这样我就可以删除“未保存”的b。

这适用于一个常见用例: 用户刷新页面。

我使用window.onunload-event通知用户他将丢失未保存的更改,并对其中的删除函数进行AJAX调用以从session-variable中删除b。 不幸的是,在删除b之前调用A控制器的索引功能。 这意味着,显示“未保存的”b,并且很快它们将被删除,这将迫使我刷新或等待以某种方式删除b。

也许我试图完成它的方式无论如何都是错误的 - 在这种情况下,我会对任何建议感到高兴。

所以问题是:如何密切关注可能被丢弃的新对象而无需将其中的每个信息存储在隐藏字段中以在保存函数上创建它们?

更新

我之前应该提到它,但我认为它并不重要。

B是一个抽象类,它由许多类扩展,如下例所示:

class childOfB extends B {
    def usefulExtraInfo
}

class anotherChildOfB extends B {
    def anotherUsefulExtraInfo
}

除此之外,B有一个整数字段,表示A中b的Set中的位置。我知道我可以使用SortedSet,但由于某些特定的原因,它必须是一个单独的字段。 我提到这一点是因为视图将每个视图呈现为可排序列表的元素,可以通过拖放重新排序。

用例:用户添加一些childOfB,anotherChildOfB并根据需要重新排序。如何在不在视图中存储类型的情况下跟踪它们的类型,这也是不好的做法,我想?

此致

1 个答案:

答案 0 :(得分:2)

  

用户需要将他的更改保存到A,否则一切都会   丢弃

听起来,当您不需要时,您正急切地创建B - 您只想在用户通过保存A确认整个操作时创建它们。

  

他可以编辑A的b的页面,他可以添加新的b

看起来您还有一个页面,其中显示所有B以进行编辑,因此无需在整个地方保留隐藏字段。

我要做的是使用普通表单输入保留视图中的所有当前更改,并调用保存A并创建/修改/删除B'的单个事务操作根据参考文献。

根据应用程序的外观,您可以通过多种方式执行此操作。

我过去使用过的一个模板(让我们说editB)接收B,索引和prefix并显示相应的输入赋予B前缀为${property}.的名称(即在编辑模式下呈现给定的B)。

A的修改视图会为editB所有B呈现B,并且:

  • 添加新的b将触发Ajax调用以检索此模板以获取新的B,前缀AB的属性名称)和对应的索引列表的长度。
  • 删除A会简单地删除与模板对应的HTML片段,并重新计算索引。

然后,在保存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}" />

<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>

编辑A

的视图
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)
        }
    }
}

<强> AController

renderNewB

用于调用B的Javascript函数,用于删除现有B的HTML片段,并且缺少处理索引但是我希望这个想法很清楚:)。

<强>更新

我假设:

  • 依赖会话获取关键信息并不是很好:会话可能会失效(例如,用户注销),并且它们不具有扩展性。
  • 为了不必在视图中携带它们而保存对象是一个糟糕的,脆弱的想法 - 它很容易破坏(会话失效,用户关闭浏览器,部署和会话不会持久)并且需要清理起来。它可以做到,但我认为成本太高了。

我认为这需要更好的客户端而不是依赖服务器技巧。您描述的更改并没有使它与众不同。

  • 将索引作为SortedSet的属性使得思考比处理List / A更容易:
    • 在显示a.b.sort { it.index }时,需要添加B以保留订单。
    • 在渲染B时,需要添加索引的隐藏输入。
    • 当拖拽'或'删除'时,需要重新计算索引的Javascript函数。
    • 绑定数据时,没有任何变化,因为索引只是一个属性。
  • B中继承真的需要将域类作为视图中的隐藏输入(或者使用一些Javascript来跟踪该信息,但我没有看到它的好处)。我不明白为什么这么糟糕。您正在使用继承作为“B类型”。如果您在type中有一个名为B的属性而不是继承,那么您可以使用输入,对吧?
    • 在呈现新domainClassName时,需要传递“类型”(B
    • 在呈现id时,如果没有A,则需要传递类名的隐藏输入
    • 保存B时,新的B是使用特定的域类创建的,否则不会发生任何变化。

我已更新代码以反映此更改。

如果我真的想提前保存对象怎么办?

如果您确信这是正确的方法,我仍会尝试避开会话并向名为confirmed的{​​{1}}添加新属性。

  • 当用户添加新B时,已确认设置为false。
  • 当用户保存A时,尚未删除的所有归属Bconfirmed设置为true,删除的已归好,已删除:)
  • 显示A时,仅显示已确认的B

即使用户关闭浏览器或会话失效,也不会向用户显示未确认的B,并且会在再次保存A时最终删除。您还可以添加一个Quartz作业,该作业会根据一些超时定期清除未经证实的B,但这很棘手 - 因为保存未确认数据的整个想法是: - )。