vanilla mvc ejemplo javascript model-view-controller design-patterns event-handling

javascript - ejemplo - ¿Manera elegante de prevenir eventos circulares en MVC?



use model in javascript mvc (2)

Esta es una pregunta difícil. Si entiendo correctamente, el problema se debe a que ha expuesto un controlador de clics en su modelo y el evento de clics del modelo es captado por el controlador. El controlador actualiza la vista, que a su vez alterna el mismo evento.

Desde mi punto de vista, consideraría inapropiado que el Controlador se una al evento Edge''s Click porque expone demasiados detalles sobre cómo se implementa y usa Edge. Al controlador no le importa cómo se usa Edge o cualquier otro detalle de implementación.

De hecho, el estilo MVC canónico no requiere que el Controlador se enganche en ningún evento de Modelo, generalmente porque el estado del Modelo no está mutado por la Vista ni por ningún otro Controlador. No es necesario que el Modelo notifique al Controlador que ha sido modificado.

Para solucionar su problema, debe definir la interfaz de View para tener un único método, como ToggleEdge:

public interface GraphView { event Action ToggleEdge; }

Es tentador querer crear dos métodos, EdgeClicked y CheckboxClicked, pero insistir en dos métodos independientes como eso viola el principio de encapsulación. Expone demasiados detalles de implementación a su Controlador o a cualquier otra persona que quiera conectarse a esos eventos. Recuerde, al controlador solo le preocupa que el estado de la vista haya cambiado, no le importa cómo haya cambiado.

Cuando implemente la interfaz View en su interfaz de usuario, debe asegurarse de que el evento ToggleEdge se invoque desde una ubicación. Para ello, enganche el evento Edge.Clicked en su Vista y utilícelo para alternar su casilla; esto hace que su casilla de verificación sea responsable de elevar la ventilación de conmutación hasta el controlador:

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); } }

Puede argumentar que la Vista sabe mucho sobre su implementación ahora: es consciente de que los bordes y las casillas de verificación están fundamentalmente conectados. Tal vez este es otro truco, pero probablemente se puede descartar porque la "lógica UI" necesita mantener sincronizada la visualización de la Vista.

La pregunta, en resumen:

En MVC, ¿cómo distinguir entre un clic de casilla (o un cuadro de selección o cambio de cuadro de lista) de un humano que significa "Controlador, modificar el modelo" y un clic de casilla (o un cuadro de selección o cambio de cuadro de lista) del Controlador que significa "I" m actualizando la vista porque el modelo ha cambiado "?

El ejemplo:

Tengo una aplicación JS (una gran página HTML + JS, hay un servidor detrás y AJAX, pero no es importante para el ejemplo) que tiene la noción de "Vértices" conectados por "Bordes". La interfaz de usuario le permite agregar y eliminar vértices en un mapa, y habilitar o deshabilitar bordes entre pares de vértices.

Hay dos formas de desactivar un borde desde el vértice A hasta el vértice B:

  1. haga clic en el borde para hacer que la ventana "Detalles del borde" le proporcione un botón "Deshabilitar este borde"; o
  2. haga clic en el Vértice A (o B) para que la ventana "Detalles del Vértice" le proporcione una lista de verificación de Vértices cercanos, desde la cual puede desmarcar Vertex B (o A).

Así es como funciona esto bajo el capó en MVC ( pero vea el final de esta publicación para obtener una actualización, donde corrijo los problemas a mi entender ):

  • Modelo: una lista de objetos Vertex y una lista de objetos Edge.
  • Ver: una UI de GMaps, con marcadores y polilíneas, además de casillas de verificación y botones y DIV "Detalles de vértice" y "Detalles de borde".
  • Controlador:
    • Funciones JS que actualizan el modelo cuando se activan eventos en las casillas de verificación y botones; y
    • Funciones de JS que actualizan la vista cuando se activan los eventos en los modelos.

Aquí está la inelegancia específica :

  1. El usuario tiene la ventana de detalles del vértice enfocada en el vértice A, y la ventana de detalles del borde se centró en el borde desde el vértice A al vértice B.
  2. El usuario hace clic en "Desactivar este borde" en la ventana Detalles del borde.
  3. La función de controlador 1 obtiene el evento de clic y las llamadas disable () en el objeto de modelo Edge.
  4. El objeto de modelo Edge desencadena el evento "Me acabo de inhabilitar".
  5. La función de controlador 2 recibe el evento "Me acaba de deshabilitar" y
    1. vuelve a dibujar la Ventana de detalles del borde para decir "¡Estoy deshabilitado!" y
    2. desmarca el Vértice B en la Ventana de detalles del vértice.
      1. ¡Mierda! ¡Esto dispara la función 1 del Controlador otra vez, que estaba escuchando eventos UI que significaban que un borde estaba deshabilitado!

Entonces, hay una nueva actualización innecesaria del Modelo y una nueva actualización de la Vista. En una vista más compleja con eventos que disparan eventos que disparan eventos, ¡esto puede generar docenas de actualizaciones extrañas!

Actualización: una gran respuesta.

Yo entendí mal MVC un poco. No tengo una sola Vista, como describí anteriormente: tengo varias Vistas en varios Modelos. En particular, tengo una vista de Lista de casillas de verificación de Bordes a un Nodo particular, y una Vista de borde separada de "estilo de ventana detallada".

Además, no debería tener una función de controlador actualizando todas las vistas cuando el Modelo cambia: cada Vista debería modificarse cuando el Modelo cambie.

Entonces, si cada Vista se registra para eventos "actualizados por estado" en el Modelo, y cada Vista se actualiza a sí misma al recibir esos eventos, entonces la respuesta a mi pregunta sobre eventos circulares es simplemente esta:

La vista de lista de casilla de verificación deshabilitará los eventos de casilla de verificación para el momento en que esté actualizando las casillas de verificación después de que cambie el estado de un Modelo.

Ahora, si un usuario deshabilita un Edge a través de la ventana Edge Detail, el Controlador actualiza el Edge Model, la vista checkbox-list recibe la notificación de la actualización, y la vista checkbox-list es lo suficientemente inteligente como para silenciar los eventos checkbox mientras cambia el estado del casilla apropiada

Esto es mucho más apetecible que mi solución original, donde un Controlador actualiza TODAS las Vistas, y por lo tanto tiene que saber qué vistas necesitan cuidados especiales y alimentación para evitar bucles. En cambio, solo la única Vista con elementos de IU problemáticos tiene que lidiar con el problema.

Gracias a quienes respondieron mi pregunta!


Solo para recapitular el modelo MVC. Las vistas generalmente deberían actualizarse. Así es como funciona: un controlador cambia el estado del modelo, el modelo envía actualizaciones a sus vistas, las vistas extraen un nuevo estado del modelo y se actualizan. Si bien los controladores y las vistas generalmente están agrupados (es decir, profundizando en los datos en una representación gráfica), nunca deberían interactuar directamente, solo a través del modelo. Esto en general, por supuesto.

Entonces, las funciones JS que actualizan sus vistas no son realmente controladores, lo cual es una distinción importante. Deben ser considerados parte de su vista. Esto podría no ser útil para el problema en cuestión, pero pensé que merecía señalarlo.

Tampoco puede eliminar su modelo, supongo que quiere decir que está eliminando algo de su modelo, ya que no hay vistas o controladores que realmente puedan existir (o estén en un estado funcional) si no están respaldados por un modelo.

Al no ser un jinete de código JS y no haber usado gmaps, realmente no veo dónde está el problema. ¿El hecho de cambiar el estado de una casilla de verificación (propiedad marcada) activa el evento onClick ()? Realmente no debería en mi humilde opinión, pero tal vez lo implementaron de esa manera, de lo contrario podría simplemente conectar su controlador a onClick () y agregar algo de lógica a la casilla (o, esto es JS, en una función en algún lugar) para cambiar el estado de la casilla de verificación . Si eso no es posible, la opción 1 y 2 son definitivamente su mejor opción.

Además: el usuario interactúa con una vista

Entonces, ¿qué sucede cuando un usuario quiere interactuar con una vista? Con frecuencia, un widget incluirá tanto una vista como el controlador. Una casilla de verificación tiene una vista (puede ver si está marcada o no) y también un controlador (puede hacer clic en él). Cuando hace clic en la casilla de verificación, en principio debería ocurrir lo siguiente:

  • el controlador de casilla de verificación recibe el evento
  • el controlador de casilla de verificación cambia el estado del valor que esta casilla de verificación representa en el modelo
  • modelo actualiza oyentes (incluida la casilla de verificación)
  • casilla de verificación actualiza su aspecto para reflejar que ese valor ha cambiado

El primer paso, cómo el controlador recibe el evento es algo dependiente del idioma, pero en los lenguajes de OOP es probable que un objeto oyente conectado a eventos de interfaz de usuario en este widget en particular que sea el controlador o notifique al controlador de la interacción del usuario.