9

Mi vista, TuneBook, tiene varias vistas secundarias del tipo ClosedTune. También tengo vistas de página completa separadas para cada canción, OpenTune. Los mismos eventos están vinculados dentro de ClosedTune y OpenTune, por lo que he diseñado mi aplicación para que ambas hereden de una vista 'abstracta' compartida Tune.Delegación de eventos a una vista principal en Backbone

Para hacer que mi aplicación más escalable Me gustaría que los eventos para cada ClosedTune a ser delegado a TuneBook, pero para el mantenimiento Me gustaría que los mismos manipuladores (los almacenados en Tune) para ser utilizados por TuneBook (a pesar de que había obviamente necesita ser envuelto en alguna función).

El problema que tengo es, dentro de TuneBook, encontrar el ClosedTune correcto para llamar al controlador. ¿Cuál es una buena manera de diseñar esto, o hay otras buenas soluciones para delegar eventos a una vista principal?

Nota - No es un duplicado de Backbone View: Inherit and extend events from parent (que es sobre los niños que heredan de una clase padre, mientras que yo estoy preguntando por los niños que son nodos secundarios de los padres en la DOM)

+0

Si los eventos van a ser ejecutados por los controladores en el modelo 'Tune' por qué quieres hacer el desvío' ChildView -> ParentView -> Model' ... puedes ejecutar 'Model.handler' directamente desde' ChildView' .. Por otro lado, puede usar el 'Modelo' como el messenger: puede modificar el estado del Modelo desde el ChildView y dejar que el ParentView escuche esta modificación. – fguillen

+0

@fguillen Es mucho menos eficiente enlazar los eventos (quizás debería ser claro me refiero a eventos DOM en lugar de eventos personalizados Backbone) al nodo DOM de cada vista secundaria en lugar de solo una vez al nodo DOM de la vista principal. En la tabla de 300 filas generada por el rendimiento de mi aplicación es notablemente lento cuando se vincula directamente a cada fila. Lo que necesito es 'ParentView -> [childView] -> childModel' – wheresrhys

Respuesta

10

En su vista principal (que también se extiende desde Backbone.Events), vincularía onEvent al evento DOM. En el desencadenador, se activará un evento de red troncal que incluye algún atributo "id" que su hijo conozca (presumiblemente, ¿alguna identificación de fila?).

var TuneBook = Backbone.View.extend(_.extend({}, Backbone.Events, { 
    events: { 
     "click .tune .button": "clickHandler" 
    }, 
    clickHandler: function (ev) { 
     this.trigger('buttonClick:' + ev.some_id_attr, ev); 
    }, 

})); 

Las vistas secundarias se suscribirían de forma natural al evento de vistas padre que les concierne. A continuación lo hago en initialize pasando la vista principal, así como el atributo especial de identificación que utilizó anteriormente en options.

var ClosedTune = Backbone.View.extend({ 

    initialize: function (options) { 
     options.parent.on('buttonClick:' + options.id, this.handler, this); 
    }, 

    handler: function (ev) { 
     ... 
    }, 

}); 

Por supuesto, puede también configurar los suscriptores similares en Tune o OpenTune.

+2

Este enfoque usa la vista padre como un "agregador de eventos", que es un buen patrón a seguir. Alternativamente, también puede crear un objeto separado que sea su agregador de eventos y pasarlo a las vistas de su hijo. Entonces sus hijos no necesitan atravesar el DOM para encontrar el objeto correcto en el que escuchar los eventos. Para obtener más información sobre los agregadores de eventos, consulte este artículo: http://lostechies.com/derickbailey/2012/04/03/revisiting-the-backbone-event-aggregator-lessons-learned/ –

+0

Esto se ve perfecto. Lo probaré esta noche – wheresrhys

+0

Implementé esto casi exactamente, con la única alteración que adjunté los detectores de eventos 'ClosedTune' dentro del método addOne() de' TuneBook' para a) evitar tener que pasar referencias al padre b) habilitar el uso de 'ClosedTune' dentro de otros contextos – wheresrhys

7

Aquí hay un par de posibilidades

1. centralizados: tienda ClosedTune objetos en el TuneBook ejemplo

  1. tienda una referencia a cada ClosedTune en tune_book.tunes. Cómo llenas tune_book.tunes depende de ti; ya que mencionó un método sumador en TuneBook, eso es lo que he ilustrado a continuación.

  2. En el controlador TuneBook caso, recuperar el ClosedTune de tune_book.tunes mediante el uso de algo así como el atributo id del destino del evento como clave. A continuación, llame al controlador Tune o ClosedTune.

http://jsfiddle.net/p5QMT/1/

var Tune = Backbone.View.extend({ 
    className: "tune", 

    click_handler: function (event) { 
    event.preventDefault(); 
    console.log(this.id + " clicked"); 
    }, 

    render: function() { 
    this.$el.html(
     '<a href="" class="button">' + this.id + '</a>' 
    ); 

    return this; 
    } 
}); 

var ClosedTune = Tune.extend({}); 

var OpenTune = Tune.extend({ 
    events: { 
    "click .button" : 'click_handler' 
    } 
}); 

var TuneBook = Backbone.View.extend({ 
    events: { 
    "click .tune .button" : 'click_handler' 
    }, 

    click_handler: function (event) { 
    var tune = this.options.tunes[ 
     $(event.target).closest(".tune").attr('id') 
    ]; 

    tune.click_handler(event); 
    }, 

    add_tune: function (tune) { 
    this.options.tunes[tune.id] = tune; 
    this.$el.append(tune.render().el); 
    }, 

    render: function() { 
    $("body").append(this.el); 
    return this; 
    } 
}); 

var tune_book = new TuneBook({ 
    tunes: {} 
}); 

[1, 2, 3].forEach(function (number) { 
    tune_book.add_tune(new ClosedTune({ 
    id: "closed-tune-" + number 
    })); 
}); 

tune_book.render(); 

var open_tune = new OpenTune({ 
    id: "open-tune-1" 
}); 

$("body").append(open_tune.render().el); 

2. descentralizada: asociar el objeto vista con el objeto DOM utilizando jQuery.data()

  1. Cuando se crea un ClosedTune, almacenar una referencia a él, por ejemplo, this.$el.data('view_object', this).

  2. En el detector de eventos, obtenga ClosedTune, p. Ej. $(event.target).data('view_object').

Se puede utilizar el mismo controlador exacto para ClosedTune (en TuneBook) y OpenTune, si lo desea.

http://jsfiddle.net/jQZNF/1/

var Tune = Backbone.View.extend({ 
    className: "tune", 

    initialize: function (options) { 
    this.$el.data('view_object', this); 
    }, 

    click_handler: function (event) { 
    event.preventDefault(); 

    var tune = 
     $(event.target).closest(".tune").data('view_object'); 

    console.log(tune.id + " clicked"); 
    }, 

    render: function() { 
    this.$el.html(
     '<a href="" class="button">' + this.id + '</a>' 
    ); 

    return this; 
    } 
}); 

var ClosedTune = Tune.extend({ 
    initialize: function (options) { 
    this.constructor.__super__.initialize.call(this, options); 
    } 
}); 

var OpenTune = Tune.extend({ 
    events: { 
    "click .button" : 'click_handler' 
    } 
}); 

var TuneBook = Backbone.View.extend({ 
    events: { 
    "click .tune .button": Tune.prototype.click_handler 
    }, 

    add_tune: function (tune) { 
    this.$el.append(tune.render().el); 
    }, 

    render: function() { 
    $("body").append(this.el); 
    return this; 
    } 
}); 

var tune_book = new TuneBook({ 
    tunes: {} 
}); 

[1, 2, 3].forEach(function (number) { 
    tune_book.add_tune(new ClosedTune({ 
    id: "closed-tune-" + number 
    })); 
}); 

tune_book.render(); 

var open_tune = new OpenTune({ 
    id: "open-tune-1" 
}); 

$("body").append(open_tune.render().el); 

respuesta al comentario

consideré la opción 1, pero decidió no hacerlo como ya tengo una colección de modelos sintonizar la tunebook y no quería otro objeto I' Necesito mantenerme sincronizado

Supongo que depende de qué tipo de limpieza/sincronización sientes que debes hacer, y por qué.

(por ejemplo, en TuneModel.remove() Me gustaría que tenga que quitar la vista de la lista de tunebook de puntos de vista ... probablemente necesitaría eventos para hacer esto, por lo que un evento única solución comienza a parecer más atractivo).

¿Por qué se siente que "tiene que quitar la vista de la lista de tunebook de puntos de vista"? (No estoy sugiriendo que no debas, solo preguntando por qué quieres querer hacerlo). Como crees, ¿cómo crees que el enfoque de @ ggozad difiere al respecto?

Ambas técnicas almacenan objetos ClosedTune en la instancia TuneBook. En la técnica de @ ggozad, está escondida detrás de una abstracción que tal vez lo haga menos obvio.

En mi ejemplo, están almacenados en un objeto JS simple (tune_book.tunes). En @ ggozad's están almacenados en la estructura _callbacks utilizada por Backbone.Events.

Adición de un ClosedTune:

1.

this.options.tunes[tune.id] = tune; 

2.

this.on('buttonClick:' + tune.id, tune.handler, tune); 

Si desea deshacerse de un ClosedTune (diga lo saque del documento con tune.remove() y quieres que el objeto de vista se haya ido por completo), usar el enfoque de @ ggozad dejará una referencia huérfana al ClosedTune i n tune_book._callbacks a menos que realice el mismo tipo de limpieza que tendría sentido con el enfoque que sugerí:

1.

delete this.options.tunes[tune.id]; 

tune.remove(); 

2.

this.off("buttonClick:" + tune.id); 

tune.remove(); 

La primera línea de cada ejemplo es opcional - dependiendo si desea limpiar los objetos ClosedTune o no.

La opción 2 es más o menos lo que estoy haciendo en este momento, pero (por otras razones) también guardo el modelo como un atributo de datos en la vista. tiene que ser una mejor manera que almacenar referencias en todo el lugar.

Bueno, en última instancia, se reduce a su preferencia sobre cómo estructurar las cosas. Si prefiere almacenar los objetos de vista de una manera más centralizada, puede almacenarlos en la instancia TuneBook en lugar de usar jQuery.data. Ver # 1: Centralizado.

un modo u otro te están las referencias a los objetos ClosedTune almacenamiento: el uso de jQuery.data, o en un objeto plano en el TuneBook, o en _callbacks en el TuneBook.

Si te gusta el enfoque de @ ggozad por razones que entiendes, ve por ello, pero no es mágico. Como se presenta aquí, no estoy seguro de qué ventaja se supone que proporciona el nivel extra de abstracción en comparación con la versión más directa que presento en el n. ° 1. Si hay alguna ventaja, no dude en escribirme.

+0

Consideré la opción 1 pero decidí no hacerlo porque ya tengo una colección de modelos de sintonización en el tunebook y no quería otro objeto que tuviera que conservar. sincronización (por ejemplo, en TuneModel.remove() Necesitaría eliminar la vista de la lista de vistas del tunebook ... probablemente necesitaría eventos para hacer esto, por lo que una solución de solo evento comienza a verse más atractiva). La opción 2 es más o menos lo que estoy haciendo en este momento, pero (por otras razones) también guardo el modelo como un atributo de datos en view. $ El, y no puedo evitar sentir que tiene que haber una mejor manera que almacenar referencias en todo el lugar. – wheresrhys

+0

@wheresrhys No sé si fue un factor en su selección, pero he corregido los errores estúpidos que estaban en mis ejemplos de código anteriores. Actualicé mi respuesta con una respuesta a su comentario (consulte la sección "Respuesta para comentar"). – JMM

+0

Saludos por la respuesta completa. Es cierto que es fácil ver los eventos como una bala mágica que combina los objetos sin inconvenientes, y tienes razón en que debería pensar más en eliminar las devoluciones de llamadas al eliminar objetos. Sin embargo, los eventos siguen siendo mi opción preferida. Tener las referencias duplicadas a los puntos de vista abstraídos se siente más limpio y menos tentador para mí o para otros desarrolladores. En cuanto al uso de $ .data, siempre tengo cuidado con el uso de cualquier cosa que consulte el DOM a menos que sea absolutamente necesario, ya que las interacciones DOM son relativamente lentas. – wheresrhys

Cuestiones relacionadas