9

mio punto di vista, TuneBook, ha diverse viste bambino di tipo ClosedTune. Dispongo anche di viste a pagina intera separate per ogni brano, OpenTune. Gli stessi eventi sono associati a ClosedTune e OpenTune, quindi ho progettato la mia app in modo che entrambi ereditino da una vista "astratta" condivisa Tune.Delega di eventi ad una vista padre in Backbone

Per rendere la mia applicazione più scalabile vorrei gli eventi per ogni ClosedTune essere delegata a TuneBook, ma per manutenibilità vorrei gli stessi gestori (quelli memorizzati nel Tune) per essere utilizzati da TuneBook (anche se ci si ovviamente devono essere racchiusi in qualche funzione).

Il problema che ho è, entro TuneBook, trovare il ClosedTune corretto per chiamare il gestore su. Qual è un buon modo per architettare questo o ci sono altre buone soluzioni per delegare eventi a una vista genitore?

Nota - non è un duplicato di Backbone View: Inherit and extend events from parent (che è di circa i bambini che ereditano dalla classe principale, mentre io sto chiedendo di bambini che sono nodi figlio della controllante nel DOM)

+0

Se gli eventi verranno eseguiti dai gestori nel modello 'Tune' perché si desidera effettuare la deviazione' ChildView -> ParentView -> Model' .. è possibile eseguire il 'Model.handler' direttamente da' ChildView'. .. In altra parte puoi usare il 'Modello' come messenger: puoi modificare lo stato del Modello da ChildView e lasciare che il ParentView ascolti questa modifica. – fguillen

+0

@fguillen È molto meno efficiente associare gli eventi (forse dovrei essere chiaro, intendo gli eventi DOM piuttosto che gli eventi personalizzati Backbone) al nodo DOM di ogni vista figlia piuttosto che una sola volta al nodo DOM della vista genitore. Sulla tabella di 300 righe generata dal rendimento della mia app è notevolmente lento quando si associa direttamente a ciascuna riga. Quello che mi serve è 'ParentView -> [childView] -> childModel' – wheresrhys

risposta

10

Nella vostra vista genitore (estesa anche da Backbone.Events), legherei onEvent all'evento DOM. In base al trigger, genererebbe un evento di backbone che include un attributo "id" che le tue visualizzazioni figlio conoscono (presumibilmente un id di riga?).

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

})); 

Le visualizzazioni figlio sarebbero quindi naturalmente iscrivere all'evento di visualizzazione padre che le riguarda. Sotto lo faccio in initialize passando la vista genitore e quell'attributo id speciale che hai usato prima in options.

var ClosedTune = Backbone.View.extend({ 

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

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

}); 

Ovviamente si può anche impostare abbonati simili su Tune o OpenTune.

+2

Questo approccio utilizza la vista genitore come "aggregatore di eventi" che è un buon modello da seguire. In alternativa, è anche possibile creare un oggetto separato che è il tuo aggregatore di eventi e passarlo alle visualizzazioni figlio. Quindi i tuoi figli non devono attraversare il DOM per trovare l'oggetto giusto su cui ascoltare gli eventi. Per maggiori informazioni su aggregatori di eventi, si veda questo articolo: http://lostechies.com/derickbailey/2012/04/03/revisiting-the-backbone-event-aggregator-lessons-learned/ –

+0

Questo sembra perfetto. Lo proveremo stasera – wheresrhys

+0

L'ho implementato quasi esattamente, con l'unica alterazione che ho allegato gli ascoltatori di eventi di "ClosedTune" all'interno del metodo addOne() di "TuneBook' a) evitando di dover passare i riferimenti a parent b) abilitare l'uso di 'ClosedTune' all'interno di altri contesti – wheresrhys

7

Ecco un paio di possibilità.

1. centralizzate: negozio ClosedTune oggetti nel TuneBook esempio

  1. memorizzare un riferimento a ciascun ClosedTune in tune_book.tunes. Come si popola tune_book.tunes dipende da voi; dal momento che hai menzionato un metodo di aggiunta su TuneBook, questo è quello che ho illustrato di seguito.

  2. Nel gestore TuneBook eventi, recuperare il ClosedTune da tune_book.tunes usando qualcosa come l'attributo id del target dell'evento come la chiave. Quindi chiamare il gestore 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. decentrata: associare l'oggetto vista con l'oggetto DOM utilizzando jQuery.data()

  1. Quando si crea un ClosedTune, memorizzare un riferimento ad esso, ad esempio, this.$el.data('view_object', this).

  2. Nel listener di eventi, recuperare il ClosedTune, per esempio $(event.target).data('view_object').

È possibile utilizzare lo stesso gestore esatta per ClosedTune (in TuneBook) e OpenTune, se si vuole.

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

risposta al commento

Ho considerato l'opzione 1, ma decise di non farlo, come ho già una collezione di modelli sintonizzare la tunebook e non volevo un altro oggetto I' D necessario mantenere la sincronizzazione

Immagino che dipenda da che tipo di gestione/sincronizzazione si sente il bisogno di fare, e perché.

(per esempio in TuneModel.remove() avrei bisogno di togliere la vista dalla lista di tunebook di punti di vista ... probabilmente avrebbe bisogno di eventi per fare questo, quindi un evento unica soluzione comincia a guardare più attraente).

Perché ti senti che è "necessario rimuovere la vista dalla lista di tunebook di punti di vista"? (Non sto suggerendo che non dovresti, solo chiedendo perché tu voglia.) Dato che lo fai, come pensi che l'approccio di @ggozad differisca da questo punto di vista?

Entrambe le tecniche memorizzano gli oggetti ClosedTune nell'istanza TuneBook. Nella tecnica di @ggozad è nascosta dietro un'astrazione che forse ti rende meno ovvio.

Nel mio esempio vengono memorizzati in un oggetto JS normale (tune_book.tunes). In @ ggozad sono memorizzati nella struttura _callbacks utilizzata da Backbone.Events.

Aggiunta di un ClosedTune:

1.

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

2.

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

Se si vuole sbarazzarsi di un ClosedTune (dite la si rimuove dal documento con tune.remove() e vuoi che l'oggetto vista sia andato completamente), usando l'approccio di @ ggozad lascerai un riferimento orfano allo ClosedTune i n tune_book._callbacks a meno che non si esegue lo stesso tipo di servizio di pulizia che avrebbe senso con l'approccio ho suggerito:

1.

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

tune.remove(); 

2.

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

tune.remove(); 

La prima riga di ogni esempio è facoltativo - a seconda se si desidera pulire gli oggetti ClosedTune o meno.

L'opzione 2 è più o meno ciò che sto facendo in questo momento, ma (per altri motivi) memorizzo anche il modello come un attributo di dati in vista. $ El, e non posso fare a meno di pensare che ci sia deve essere un modo migliore di memorizzare i riferimenti dappertutto.

Bene, alla fine si riduce alle preferenze su come strutturare le cose. Se si preferisce archiviare gli oggetti di visualizzazione in modo più centralizzato, è possibile memorizzarli nell'istanza TuneBook invece di utilizzare jQuery.data. Vedi n. 1: centralizzato.

un modo o nell'altro sei memorizzare i riferimenti agli oggetti ClosedTune: utilizzando jQuery.data, o in un oggetto pianura nel TuneBook, o in _callbacks nel TuneBook.

Se ti piace l'approccio di @ggozad per ragioni che comprendi, prova a farlo, ma non è magico. Dato che è presentato qui non sono sicuro di quale vantaggio debba essere fornito dal livello extra di astrazione rispetto alla versione più semplice che presento in # 1. Se c'è qualche vantaggio, sentitevi liberi di riempirmi.

+0

Ho preso in considerazione l'opzione 1 ma ho deciso contro di esso poiché ho già una collezione di modelli di melodia nel tunebook e non volevo un altro oggetto che avrei dovuto tenere in sync (es. in TuneModel.remove() Avrei bisogno di rimuovere la vista dall'elenco delle viste di tunebook ... probabilmente avrebbe bisogno di eventi per farlo, quindi una soluzione solo per gli eventi inizia a sembrare più attraente). L'opzione 2 è più o meno ciò che sto facendo in questo momento, ma (per altri motivi) memorizzo anche il modello come un attributo di dati in vista. $ El, e non posso fare a meno di pensare che ci deve essere un modo migliore di memorizzare i riferimenti dappertutto. – wheresrhys

+0

@wheresrhys Non so se è stato un fattore nella tua selezione, ma ho corretto gli errori stupidi che erano nei miei precedenti esempi di codice. Ho aggiornato la mia risposta con una risposta al tuo commento (vedi la sezione "Risposta al commento"). – JMM

+0

Ciao per la risposta completa. È vero che è facile vedere gli eventi come una bacchetta magica che accoppia liberamente gli oggetti senza alcun svantaggio, e hai ragione che dovrei pensare più a declutternig i callback quando rimuovi gli oggetti. Tuttavia, gli eventi sono ancora la mia opzione preferita. Avere i riferimenti duplicati alle viste astratte si sente più pulito e meno allettante per me o per gli altri sviluppatori di abusare. Per quanto riguarda l'utilizzo di $ .data, sono sempre cauto nell'usare qualsiasi cosa che richieda il DOM a meno che non sia assolutamente necessario poiché le interazioni DOM sono relativamente lente. – wheresrhys