2011-12-05 25 views
7

Estoy empezando con Backbone. Pasé por las dos primeras capturas de pantalla de PeepCode, que fueron geniales y ahora estoy investigando en una maqueta de una aplicación futura separada (no en el lado del servidor).Backbone Design

Esto es lo que estoy buscando construir (más o menos). Una serie de cinco cuadros de texto: llamemos a estos widgets. Cada entrada de widgets, cuando se seleccione, mostrará un panel que muestra las tareas asociadas con el widget y le permite al usuario crear una nueva tarea o destruir las tareas existentes.

En este punto, estoy pensando que tengo los siguientes modelos:

Widget 
Task 

las siguientes colecciones:

Tasks 
Widgets 

las vistas siguientes (aquí es donde se pone peluda!)

WidgetListView 
    - Presents a collection of Widgets 
WidgetView 
    - sub-view of WidgetListView to render a specific Widget 
TaskPaneView 
    - Presented when the user selects a Widget input 
TaskCreateView 
    - Ability to create a new Task associated with selected Widget 
TaskListView 
    - Presents a collection of Tasks for the given widget 
TaskView 
    - Displays Task detail - sub-view of TaskListView 

Suponiendo que sea razonable, el truco se convierte en cómo mostrar un TaskPaneView cuando se ve un WidgetView seleccionado Y además, cómo ese TaskPaneView debería a su vez renderizar TaskCreateViews y TaskListViews.

La verdadera pregunta aquí es: ¿una cascada rinde eventos a través de vistas? ¿Está permitido que una vista Root conozca las sub-vistas y las represente explícitamente? ¿Debería ser esto impulsado por eventos?

Disculpa si esta es una pregunta abierta, solo esperando que alguien haya visto algo similar antes y pueda apuntarme en la dirección correcta.

Gracias!

+1

es posible que desee comprobar Marionette por la misma razón @Luc Perkins sugirió Backbone Aura. Marionette puede ser un poco más maduro que Aura –

+0

Creo que TaskCreateView y TaskPaneView son innecesarios. TaskCreateView, en particular, no debería estar allí, ya que no tiene ningún modelo para representar. Incorpore la creación de tareas en TaskListView. Tal vez un control de entrada que permita a los usuarios agregar nuevas tareas. Y cuando el usuario presione la tecla Entrar, agréguelo a la colección Tareas. Estoy asumiendo que está escuchando para agregar eventos en la colección, para que pueda agregar el TaskView recién creado al TaskListView. Puede mostrar TaskListView siempre que el usuario se enfoque en WidgetView. – Vishal

Respuesta

15

Definitivamente hacerlo impulsado por eventos. Además, trate de no crear vistas que estén estrechamente conectadas. El acoplamiento flojo hará que su código sea más fácil de mantener y flexible.

Control hacia fuera este post sobre el modelo de eventos agregador y la columna vertebral:

http://lostechies.com/derickbailey/2011/07/19/references-routing-and-the-event-aggregator-coordinating-views-in-backbone-js/

La versión corta es que usted puede hacer esto:

var vent = _.extend({}, Backbone.Events); 

y utilizar vent.trigger y vent.bind para controlar tu aplicación

+0

Muy interesante. Volveré con un voto positivo. ¿Alguna idea de si es genial o no que una vista de raíz tenga subvistas? Por ejemplo, ¿tiene TaskPaneView, que incluiría Create y List, incluso tiene sentido? – Cory

+1

no hay problema en absoluto, las vistas anidadas son muy posibles, y solo puedo hablar por mí mismo diciendo que las uso a menudo, el ejemplo más fácil sería tener un catálogo de libros, con un ListView y cada elemento como una vista por sí mismo, pero puede irse más allá de eso. – Sander

+1

Es genial ya que funciona. No es genial ya que será más difícil mantener tu código. Modular y usar eventos para desencadenar renders. Si usa el patrón Agg de eventos, puede hacer algo como vent.trigger ('crear', modelo); que pasará el modelo a las devoluciones de llamada vinculadas (en este caso: una función que representa una vista). En esta situación, su vista de creación ahora es un componente reutilizable de su aplicación a la que puede llamar desde cualquier lugar. –

0

Desde una perspectiva clásica de MVC, sus vistas responden a los cambios en sus modelos asociados.

//initialize for view 
initialize : function() { 
    this.model.on("change", this.render(), this); 
} 

La idea aquí es que cada vez que se modifique el modelo de una vista, se renderizará solo.

Alternativa o adicionalmente, si cambia algo en una vista, puede desencadenar un evento que el controlador escucha. Si el controlador también creó los otros modelos, puede modificarlos de alguna manera significativa, luego, si está escuchando los cambios en los modelos, las vistas también cambiarán.

+1

Es interesante ver que puedes extender Backbone.Events. Yo mismo utilizo una implementación 'Pure JS pub/sub' que obtuve de aquí: http://jsperf.com/pubsubjs-vs-jquery-custom-events/26 Realmente sugiero verificarlo para cualquier persona en absoluto en el desempeño de pub/sub o javascript. – Mosselman

+1

En realidad, no puede extender Backbone.Events. Es simplemente un hash que puede agregar al prototipo de objeto personalizado para permitirle enviar y recibir eventos. –

0

Respuesta similar a la de Chris Biscardi.Aquí es lo que tengo:

se crea un despachador var mundial (no tiene que ser global, siempre que se puede acceder desde el ámbito de aplicación Backbone):

Dispatcher = _.extend({}, Backbone.Events); 

despachador le ayudará a ejecutar devoluciones de llamada suscritas con eventos que no se desencadenan particularmente por cambios en modelos o colecciones. Y lo bueno es que Dispatcher puede ejecutar cualquier función, dentro o fuera de la aplicación Backbone.

se suscribe a eventos usando bind() en una vista o cualquier parte de la aplicación:

Dispatcher.bind('editor_keypress', this.validate_summary); 

Luego, en otra vista o parte de la aplicación que desencadenan nuevo evento usando gatillo():

Dispatcher.trigger('redactor_keypress'); 

La belleza de utilizar un despachador es su simplicidad y capacidad de suscribir múltiples oyentes (por ejemplo, devoluciones de llamada en diferentes vistas de Backbone) para el mismo evento.

12

p.s Pre .: He hecho una esencia para usted con el código que escribí a continuación: https://gist.github.com/2863979

Estoy de acuerdo con el pub/sub ('patrón Observer') que se sugiere por las otras respuestas. Sin embargo, también usaría el poder de Require.js junto con Backbone.

¡Addy Osmani ha escrito algunos EXCELENTES! recursos sobre patrones de diseño Javascript y sobre la construcción de aplicaciones de red troncal:

http://addyosmani.com/resources/essentialjsdesignpatterns/book/ http://addyosmani.com/writing-modular-js/ http://addyosmani.github.com/backbone-fundamentals/

Lo bueno de usar AMD (implementado en Require.js) junto con Backbone es que a resolver algunos problemas que usted' d normalmente tiene.

Normalmente definirá clases y almacenarlos en una especie de forma de espacios de nombres, por ejemplo:

MyApp.controllers.Tasks = Backbone.Controller.extend({}) 

Esto está bien, siempre y cuando se define la mayoría de las cosas en un solo archivo, cuando se inicia la adición de más y más diferentes archivos a la mezcla se vuelve menos robusto y debe comenzar a prestar atención a cómo se carga en diferentes archivos, controllers\Tasks.js después de models\Task.js etc. Por supuesto, puede compilar todos los archivos en el orden correcto, etc., pero está lejos de ser perfecto.

Además de esto, el problema con el modo no AMD es que tiene que anidar Vistas dentro de la otra con más fuerza. Digamos:

MyApp.classes.views.TaskList = Backbone.View.extend({ 
    // do stuff 
}); 

MyApp.views.App = Backbone.View.extend({ 
    el: '#app', 
    initialize: function(){ 
     _.bindAll(this, 'render'); 
     this.task_list = new MyApp.classes.views.TaskList();  
    }, 

    render: function(){ 
     this.task_list.render(); 
    } 
}); 

window.app = new MyApp.views.App(); 

Todo bien y bien, pero esto puede convertirse en una pesadilla.

Con AMD puede definir un módulo y darle un par de dependencias, si usted está interesado en cómo funciona este leen los enlaces de arriba, pero el ejemplo anterior sería el siguiente:

// file: views/TaskList.js 
define([], function(){ 

    var TaskList = Backbone.View.extend({ 
     //do stuff 
    }); 

    return new TaskList(); 
}); 

// file: views/App.js 
define(['views/TaskList'], function(TaskListView){ 

    var App = Backbone.View.extend({ 
     el: '#app', 

     initialize: function(){ 
      _.bindAll(this, 'render'); 
     }, 

     render: function(){ 
      TaskListView.render(); 
     } 
    }); 

    return new App(); 
}); 

// called in index.html 
Require(['views/App'], function(AppView){ 
    window.app = AppView; 
}); 

en cuenta que en el caso de puntos de vista que le devuelven instancias, voy a hacer esto colecciones también, pero para los modelos que me gustaría volver clases:

// file: models/Task.js 
define([], function(){ 

    var Task = Backbone.Model.extend({ 
     //do stuff 
    }); 

    return Task; 
}); 

esto puede parecer un poco mucho al principio, y la gente puede pensar 'wow esto es excesivo '. Pero el verdadero poder se hace evidente cuando se tiene que utilizar los mismos objetos en muchos módulos diferentes, por ejemplo, las colecciones:

// file: models/Task.js 
define([], function(){ 

    var Task = Backbone.Model.extend({ 
     //do stuff 
    }); 

    return Task; 
}); 

// file: collections/Tasks.js 
define(['models/Task'], function(TaskModel){ 

    var Tasks = Backbone.Collection.extend({ 
     model: TaskModel 
    }); 

    return new Tasks(); 
}); 

// file: views/TaskList.js 
define(['collections/Tasks'], function(Tasks){ 

    var TaskList = Backbone.View.extend({ 
     render: function(){ 
      _.each(Tasks.models, function(task, index){ 
       // do something with each task 
      }); 
     } 
    }); 

    return new TaskList(); 
}); 


// file: views/statistics.js 
define(['collections/Tasks'], function(Tasks){ 

    var TaskStats = Backbone.View.extend({ 
     el: document.createElement('div'), 

     // Note that you'd have this function in your collection normally (demo) 
     getStats: function(){ 
      totals = { 
       all: Tasks.models.length 
       done: _.filter(Tasks, function(task){ return task.get('done'); }); 
      }; 

      return totals; 
     }, 

     render: function(){ 
      var stats = this.getStats(); 

      // do something in a view with the stats. 
     } 
    }); 

    return new TaskStats(); 
}); 

Nota ese objeto las 'tareas' es exactamente la misma en ambos puntos de vista, por lo que los mismos modelos, estado, etc. Esto es mucho mejor que tener que crear instancias de la colección Tareas en un punto y luego hacer referencia a ella a través de toda la aplicación todo el tiempo.

Al menos para mí el uso de Require.js con Backbone me ha quitado una pieza gigantesca del enigma con dónde crear una instancia de qué. Usar módulos para esto es muy útil. Espero que esto sea aplicable a su pregunta también.

p.s. tenga en cuenta que también incluiría Backbone, Underscore y jQuery como módulos en su aplicación, aunque no es necesario, puede cargarlos utilizando las etiquetas de script normales.

+0

Gran respuesta hombre, hiciste mi día. – mamoo

+0

@mamoo gracias! Me alegro de poder ayudar. Gracias por dejarme saber que te gusta. – Mosselman

+0

Gran respuesta. También- Perry the Platypus rocks. – jerrygarciuh

1

Para algo con este tipo de complejidad, podría recomendar el uso de Backbone Aura, que aún no tiene una versión de lanzamiento estable. Aura esencialmente le permite tener múltiples aplicaciones Backbone totalmente autónomas, llamadas "widgets", que se ejecutan en una sola página, lo que podría ayudar a desentrañar y suavizar parte de su lógica de modelo/vista.