优雅的方法来防止MVC中的循环事件?

时间:2008-12-17 03:58:03

标签: javascript model-view-controller design-patterns event-handling

问题,简而言之:

在MVC中,如何区分复选框点击(或选择框或列表框更改)与人类意义“控制器,修改模型”以及从控制器含义复选框(或选择框或列表框更改) “我正在更新视图,因为模型已更改”?


示例:

我有一个JS应用程序(所有一个大的HTML + JS页面;它背后有一个服务器,AJAX正在进行,但对于这个例子来说并不重要),其中“顶点”的概念由“边缘”连接。用户界面允许您在地图上添加和删除顶点,并启用或禁用顶点对之间的边线。

有两种方法可以禁用从顶点A到顶点B的边缘:

  1. 单击Edge以使“Edge Details”窗口为您提供“Disable This Edge”按钮;或
  2. 单击“顶点A”(或B)以使“顶点详细信息”窗口为您提供附近顶点的清单,您可以从中取消选中顶点B(或A)。
  3. 这是如何在MVC()下工作的,但请参阅本文末尾的更新,我在理解中纠正了问题

    • 模型:Vertex对象列表和Edge对象列表。
    • 查看:带有标记和折线的GMaps UI,以及复选框和按钮以及“顶点细节”和“边缘细节”DIV。
    • 控制器:
      • JS功能,当复选框和按钮上的事件触发时更新模型;和
      • JS函数,用于在模型上的事件触发时更新视图。

    这是具体的不雅

    1. 用户的顶点细节窗口侧重于顶点A,边缘细节窗口侧重于从顶点A到顶点B的边缘。
    2. 用户在“边缘详细信息”窗口中单击“禁用此边缘”。
    3. 控制器功能1获取click事件,并在Edge模型对象上调用disable()。
    4. Edge模型对象触发“我刚被禁用”事件。
    5. 控制器功能2接收“我刚被禁用”事件,并且
      1. 重新绘制边缘详细信息窗口,说“我已禁用!”和
      2. 在“顶点详细信息”窗口中取消选中“顶点B”。
        1. 废话!这再次触发Controller功能1,正在侦听意味着禁用边缘的UI事件!
    6. 因此,不必要的重新更新模型,并重新更新View。在一个更复杂的视图中,事件会触发触发事件的事件,这可能会导致数十个无关的更新!

      <小时/> 更新:一个很好的答案。

      我误解了MVC。我没有一个View,如上所述:我有几个模型的几个视图。特别是,我有一个特定节点的复选框列表边缘视图,以及一个单独的“详细窗口式”边缘视图。

      此外,我不应该在模型更改时更新所有视图的一个控制器功能:每个视图应在模型更改时修改本身

      因此,如果每个View在Model上注册“状态更新”事件,并且每个View在收到这些事件后自行更新,那么我的循环事件问题的答案就是这样:

      复选框列表视图会在模型状态更改后更新复选框时禁用复选框事件。

      现在,如果用户通过“边缘详细信息”窗口禁用边缘,则控制器会更新边缘模型,复选框列表视图会收到更新通知,复选框列表视图足够智能,可以在更改时隐藏复选框事件相应复选框的状态。

      这比我原来的解决方案更加可口,其中一个Controller更新所有视图 - 因此必须知道哪些视图需要特别小心和喂养以避免循环。相反,只有具有麻烦的UI元素的单个View必须处理该问题。

      感谢那些回答我问题的人!

2 个答案:

答案 0 :(得分:3)

回顾一下MVC模型。观点通常应该自我更新。以下是它的工作原理:控制器更改模型的状态,模型将更新发送到其视图,视图从模型中获取新状态并自行更新。虽然控制器和视图通常是捆绑在一起的(即以图形表示形式向下钻取数据),但它们不应仅通过模型直接交互。这当然是一般的。

因此更新视图的JS函数实际上不是控制器,这是一个重要的区别。它们应被视为您观点的一部分。这可能对手头的问题没有帮助,但我认为值得指出。

您也不能删除您的模型,我认为您的意思是您正在从模型中删除某些,因为如果没有任何视图或控制器可以存在(或处于功能状态)没有模特支持。

不是JS代码骑师并没有使用gmaps我真的没有看到问题出在哪里。更改复选框(已检查属性)的状态是否会触发onClick()事件?它真的不应该恕我直言,但也许他们这样实现它,否则你可以只是将你的控制器附加到onClick()并添加一些逻辑到复选框(或者,这是JS,在一个函数的某个地方)来改变复选框状态。如果那是不可能的,选项1和2肯定是你最好的选择。

另外:用户与视图交互

那么当用户想要与视图交互时会发生什么?通常,窗口小部件将包括视图和控制器。复选框有一个视图(您可以查看是否已选中)以及控制器(您可以单击它)。当您单击复选框时,原则上应该发生以下情况:

  • 复选框控制器接收事件
  • 复选框控制器更改此复选框在模型中表示的值的状态
  • 模型更新侦听器(包括复选框)
  • 复选框更新其外观以反映该值已更改

第一步,控制器如何接收事件在某种程度上依赖于语言,但在OOP语言中,它可能是一个附加到此特定小部件上的用户界面事件的侦听器对象,该小部件是控制器或通知控制器用户交互。

答案 1 :(得分:0)

这是一个艰难的。如果我理解正确,则会导致问题,因为您在模型上公开了单击处理程序,并且控制器捕获了模型的单击事件。控制器更新视图,然后切换相同的事件。

从我的观点来看,我认为控制器将自己附加到Edge的Click事件是不合适的,因为它暴露了有关Edge如何实现和使用的过多细节。 Controller不关心Edge的使用方式或任何其他实现细节。

实际上,规范的MVC样式根本不需要Controller挂接到任何模型事件,通常是因为View的状态不会被View或任何其他控制器变异。模型无需通知控制器它已被更改。

要解决您的问题,您应该定义View的界面以使用单一方法,例如ToggleEdge:


public interface GraphView
{
    event Action ToggleEdge;
}

很想要创建两个方法,EdgeClicked和CheckboxClicked,但坚持使用两个独立的方法,这违反了封装原则。它向Controller或任何想要挂钩这些事件的人暴露了太多的实现细节。请记住,Controller只关心View的状态发生变化,它不关心 它的变化。

在用户界面上实现View界面时,应注意确保从一个位置调用ToggleEdge事件。您可以通过挂钩View中的Edge.Clicked事件并使用它来切换复选框来完成此操作;这使得您的复选框负责将Toggle通风口提升到控制器:


public class UI : UserControl, GraphView
{
    public event Action ToggleEdge;

    void OnToggleEdge(Edge edge)
    {
        if (ToggleEdge != null)
            ToggleEdge(edge);
    }

    protected void Edge_Clicked(object sender, EventArgs e)
    {
        CheckBox chkbox = FindCheckBoxThatCorrespondsToEdge((Edge)sender);
        chkbox.Checked = !chkbox.Checked;    
    }

    protected void chkEdge_CheckChanged(object sender, EventArgs e)
    {
        Edge edge = FindEdgeThatCorrespondsToCheckbox((CheckBox)sender);
        OnToggleEdge(edge);
    }
}

你可以提出一个观点,即View现在对它的实现了解得太多了:它知道边和复选框是基本连接的。也许这是另一个黑客,但它可能被解雇,因为“UI逻辑”需要保持View的显示同步。