Backbone when to build logic into view vs having separate view - backbone.js

So I know there are many ways for doing things in Backbone, but since I don't have the vast experience of many on here, I would like advice regarding a best approach.
I have a basic CRUD app where I have a form for adding data and a form for editing data, which are essentially the same. And actually are attached to the same DOM element.
So I could include the two forms into one view and serve the appropriate template, depending on whether the view has a model or not. Something like this:
var AddEditView = Backbone.View.extend ({
el: $("#add"),
template: $("editTemplate").html();
render: function() {
if (this.model) {
var theTmp = _.template(this.template)
this.$el.html(theTmp(this.model.toJSON()));
}
else {
var theTmp = _.template($("#addTemplate").html());
this.$el.html(theTmp);
}
},
});
I think this would work however I might run into some sticky issues with any events (i.e. events being bound twice).
Or I could create two completely separate views that would be attached to the same dom element and would toggle back and forth depending on the app state (way more lines of code).
What would you all recommend in this situation. Is more logic in a view better or more lines of code and less logic in views?

Assuming the fields are identical whether you're creating a new model or editing an existing one, I'd recommend having only one view, and only one template. When you instantiate the view, if you are creating a new model, just instantiate a new model and pass it to the view. Otherwise, pass the existing model.
var AddEditView = Backbone.View.extend ({
render: function() {
var theTmp = _.template(this.template)
this.$el.html(theTmp(this.model.toJSON()));
},
someSaveEvent: function() {
this.model.save(); // This will POST if the model isNew() or PUT otherwise
}
});
var model = new Model(); // If you're creating a new model
var addEditView = new AddEditView({
model: model
});

Related

Using same object instance from more than one different Backbone/Require views

I try to write two views login view and register view each renders with same data which is collected from a keyup dom event in both views. Login template has less data field, so less template variables, than register template does.
So I thought of using one model for gui render of those two, forms data model. I am trying to separate the concerns and keep single responsibility for my modules. Now, it is needed to make forms data model a dependency of one view and create in this view, and mention this same instance from the other by making the former view a dependency of this other view. Yet this last should use the same instance of the view it has as a dependency. It seems having only one view and three separate models forms data, login and register and two different templates can be a solution.
This seems surprising. In object orientated paradigm it feels adding a little feature makes its very fast cumbersome, which conflicts with the object orientation ideas. All this feature is for when user clicks register button in the login view the register view render with the just entered data and vice versa.
Is that usage, referencing one view/model from another one for using its models etc., is a wrong use of the Backbone or Require while they are meant to utilise good developing practices such as separation of concerns, single responsibility, and modularisation separation, or that is not bad at all regarding those good dev practices?
I don't think it's "wrong" per say to have View1 depend on View2 so that View1 can access a Model that View2 creates ... but I do think it's sub-optimal. I can think of two other strategies you might want to consider that would let you avoid making View1 depend on View2.
First, you could opt not to create the Model in View2, and instead just populate it in View2 ... and create it beforehand in its own module. In other words:
// theLoginData.js
define(['LoginData'], function(LoginData) {
return new LoginData();
});
// View #2
define(['theLoginData'], function(theLoginData) {
return Backbone.View.extend({
someMethod: function() {
theLoginData.set(whatever);
}
});
});
// View #1
define(['theLoginData'], function(theLoginData) {
return Backbone.View.extend({
someOtherMethod: function() {
var whatever = theLoginData.get('whatever');
}
});
});
The other option would be to still create the Model in View2, but "register" it at a more global scope (eg. as a static property on the Model class you use for your login data Model):
// LoginData.js (ie. the Model class for your login data)
define([], function() {
return Backbone.Model.extend({}, {
currentLoginData: null
});
});
// View #2
define(['LoginData'], function(LoginData) {
return Backbone.View.extend({
someMethod: function() {
LoginData.currentLoginData = new LoginData(whatever);
}
});
});
// View #1
define(['LoginData'], function(LoginData) {
return Backbone.View.extend({
someOtherMethod: function() {
var whatever = LoginData.currentLoginData.get('whatever');
}
});
});

How do you maintain view states with Marionette JS?

It seems that Marionette isn't designed to handle reusing views. What is an effective way to maintain view states (for all view types) since they're always being reconstructed?
My first thought was to capture state in the models, but I quickly parted with that idea as many views can reference the same model.
I did try reusing views in a couple of different fashions but wasn't confident what I was doing was correct. In each case, I felt like the Marionette methods I was overriding might break things (now and/or later).
I've found suggestions on how to accomplish this with pure Backbone, and also glanced at Giraffe which seems to handle state quite effectively, but I've yet to find a solution for Marionette.
An easy unsaved state system for getting data to the template is:
View.MyView = Marionette.ItemView.extend({
serializeData: function() {
return _.merge(this.model.toJSON(), this.options.state.toJSON());
},
});
var view = new View.MyView({
model: model,
state: new Backbone.Model(),
});
With a little more advanced version you can use the model events too:
View.MyView = Marionette.ItemView.extend({
initialize: function() {
var self = this;
this.options.state.on('change', function(event, data) {
if (_.isFunction(self[event])) {
self[event](data);
}
});
},
});
Then in your controller:
view.options.state.trigger('change', 'functionName', 'someData');
You can track your view states by using a ViewModel.
Sam talks a bit about this here: http://youtu.be/CTr-tTwRH3o

How to avoid duplicate code and logic in templates for similar views in Backbone.js

I am building an app using backbone.js and find myself with a view with a lot of conditional logic in the templating of the Model's View. The type property of the model is used to determine which html to render. I would like to avoid this logic if possible, because it is hard to read. There are a couple of ways that I think I could deal with this (Note that the actual templates I've got here are very much simplified):
1. Conditional Logic in Collection View rather than in Model View - Multiple Subviews
I could put the conditional logic that acts on each model's type into the Collection View:
var CollectionView = Backbone.View.extend({
....
render: function() {
this.collection.each(function(thing) {
if(thing.get("type") === "wotsit") {
this.$el.append(new WotsitView({ model: thing });
} else if(thing.get("type") === "oojamaflip") {
this.$el.append(new OojamaflipView({ model: thing });
}
}, this);
},
....
}
Pros
This way I could have each subview with a template method that had no logic in it, but rather just builds html.
var WotsitView = new Backbone.View.extend({
....
template: _.template('<h2>{{ title }}</h2>');
});
var OojamaflipView = new Backbone.View.extend({
....
template: _.template('<h3>{{ title }}</h3>');
});
Cons
The thing is, the things in the collection are all very similar. The events for each thing are likely to be the same or very similar and I can see there being a lot of code duplication. I really only want the actual template for these subviews to be different with everything else the same.
2. Conditional Logic in Model View - Multiple Template Methods
var ModelView = Backbone.View.extend({
....
render: function() {
if(this.model.get("type") ==== "wotsit") {
this.$el.html(this.wotsitTemplate(this.model.attributes));
} else if(this.model.get("type") === "oojamaflip") {
this.$el.html(this.oojamaflipTemplate(this.model.attributes));
}
},
wotsitTemplate: _.template('<h2>{{ title }}</h2>'),
oojamaflipTemplate: _.template('<h3>{{ title }}</h3>')
});
Pros
There is only one view for the model.
All of the events etc are handles in one view rather that being duplicated.
Cons
I actually quite like this way, but I would be very interested to hear some other peoples options on it.
Option #1 = polymorphism
Option #2 = switch statement
Switch statements are useful if you never (or rarely) have to add new types, but you might want to add new methods. Imagine you want to add a validate method to ModelView, which checks view input for that kind of model for correctness and reports the results to the user. With option #2, you'd just add one new method that switches on the model type (just like the render method) to handle validation.
Now let's assume we already have a render method and a validate method, and we want to handle a new type of model, a thingamajig. With option #2, you'd have to add logic to both render and validate. Now imagine we don't have just 2 methods, but 10 — option #2 gets real complicated real quick when you have to handle new types. But if we followed option #1, then no matter how many methods there were, we'd only have to create a new view in one place, and update CollectionView to map the new type to the new view.
Most of the time polymorphism (option #1) is the clean way to go. It lets you separate all logic specific to a given type and put it one place, making it easy to handle new types.
Keeping DRY
If you're worried with option #1 that you'll end up with a lot of duplicate code between all the views, don't forget that it's easy to make Views that inherit from other Backbone Views:
var ItemView = Backbone.View.extend({
initialize: function() {
/* some common logic for all views */
}
});
var WotsitView = ItemView.extend({
template: _.template('<h2>{{ title }}</h2>')
});
var OojamaflipView = ItemView.extend({
template: _.template('<h3>{{ title }}</h3>')
});

How to determine if I should use a layout view Marionette?

I have skimmed through all the marionette articles on the layout view and I am not sure if there are advantages to using that versus how I have my app setup now, let me show you.
After building the components to my app I then created an app level view that handles the initialization and rending of all those views.
var Backbone = require('backbone'),
TeamsView = require('./teams'),
DataView = require('./teamData'),
LeaderView = require('./leader');
module.exports = appView = Backbone.View.extend({
el: '#wrap',
template: require('../../templates/app.hbs'),
initialize: function() {
window.App.views.teamsView = new TeamsView({ collection: window.App.data.teams });
window.App.views.dataView = new DataView({ collection: window.App.data.teams });
window.App.views.leaderView = new LeaderView({ collection: window.App.data.teams });
},
render: function() {
var teamsView = window.App.views.teamsView;
var dataView = window.App.views.dataView;
var leaderView = window.App.views.leaderView;
this.$el.find('#basketball .app').prepend(teamsView.render().el);
this.$el.find('#basketball .app').prepend(dataView.render().el);
this.$el.prepend(leaderView.render().el);
}
});
Then inside a controller I render the app view above.
This feels comfortable to me, but somewhere deep inside says it's wrong and I should be looking at layout views?
So my question more specifically is when putting the pieces together in an backbone application should I be looking into layout views or is creating a single app level view (like above) sufficient?
What you're doing is fine at the simplest level, though perhaps more manual than it needs to be. However, you may want a Layout View as things get more complex.
The value of a Layout View comes when you have a region in your App that you'd like to have contain sub-regions, but only during certain views. For example, you might have an index/list inside of your App's #mainRegion that just has a bunch of teams, in which case you could use a CollectionView (or CompositeView if you want to add some styling) along with the ItemView for each team.
However, say you click to edit one of the teams, and now you want the #mainRegion to show the edit page, which itself has some information about the team in #infoRegion and then an edit form in a #formRegion. These are regions that are specific to the edit page, and so you'd want to use a Layout View to manage them rather than delegating all the way up to the App level.
I hope this makes sense, and I'm happy to clarify if needed. You can also check out the documentation for a breakdown of when to use each view type: https://github.com/marionettejs/backbone.marionette/wiki/Use-cases-for-the-different-views

Backbone Design

I'm just getting started with Backbone. I went through the first two PeepCode screencasts which were great and now I'm digging in on a quick detached (no server side) mock-up of a future app.
Here's what I'm looking to build (roughly). A series of five text boxes - lets call these Widgets. Each Widget input, when selected, will display a pane that shows Tasks associated with the Widget and allow the user to create a new Task or destroy existing Tasks.
At this point, I'm thinking I have the following models:
Widget
Task
The following collections:
Tasks
Widgets
The following views (this is where it gets hairy!)
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
Assuming that's reasonable, the trick becomes how to display a TaskPaneView when a WidgetView is selected. And futhermore, how that TaskPaneView should in turn render TaskCreateViews and TaskListViews.
The real question here is: Does one cascade render events across Views? Is it permissible for a Root view to know of sub-views and render them explicitly? Should this be event-driven?
Apologies if this is an open-ended question, just hoping someone will have seen something similar before and be able to point me in the right direction.
Thanks!
Definitely make it event driven. Also, try not to create views that are closely coupled. Loose coupling will make your code more maintainable as well as flexible.
Check out this post on the event aggregator model and backbone:
http://lostechies.com/derickbailey/2011/07/19/references-routing-and-the-event-aggregator-coordinating-views-in-backbone-js/
The short version is you can do this:
var vent = _.extend({}, Backbone.Events);
and use vent.trigger and vent.bind to control your app.
Pre p.s.: I have made a gist for you with the code that I wrote below:
https://gist.github.com/2863979
I agree with the pub/sub ('Observer pattern') that is suggested by the other answers. I would however also use the power of Require.js along with Backbone.
Addy Osmani has written a few GREAT! resources about Javascript design patterns and about building Backbone applications:
http://addyosmani.com/resources/essentialjsdesignpatterns/book/
http://addyosmani.com/writing-modular-js/
http://addyosmani.github.com/backbone-fundamentals/
The cool thing about using AMD (implemented in Require.js) along with Backbone is that you solve a few problems that you'd normally have.
Normally you will define classes and store these in some sort of namespaced way, e.g.:
MyApp.controllers.Tasks = Backbone.Controller.extend({})
This is fine, as long as you define most things in one file, when you start adding more and more different files to the mix it gets less robust and you have to start paying attention to how you load in different files, controllers\Tasks.js after models\Task.js etc. You could of course compile all the files in proper order, etc, but it is far from perfect.
On top of this, the problem with the non AMD way is that you have to nest Views inside of each other more tightly. Lets say:
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();
All good and well, but this can become a nightmare.
With AMD you can define a module and give it a few dependencies, if you are interested in how this works read the above links, but the above example would look like this:
// 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;
});
Notice that in the case of views you'd return instances, I do this for collections too, but for models I'd return classes:
// file: models/Task.js
define([], function(){
var Task = Backbone.Model.extend({
//do stuff
});
return Task;
});
This may seem a bit much at first, and people may think 'wow this is overkill'. But the true power becomes clear when you have to use the same objects in many different modules, for example collections:
// 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();
});
Note that the 'Tasks' object is exactly the same in both views, so the same models, state, etc. This is a lot nicer than having to create instances of the Tasks collection at one point and then reference it through the whole application all the time.
At least for me using Require.js with Backbone has taken away a gigantic piece of the puzzling with where to instantiate what. Using modules for this is very very helpful. I hope this is applicable to your question as well.
p.s. please also note that you'd include Backbone, Underscore and jQuery as modules to your app too, although you don't have to, you can just load them in using the normal script tags.
For something with this kind of complexity, I might recommend using Backbone Aura, which has not yet had a stable release version. Aura essentially allows you to have multiple fully self-contained Backbone apps, called "widgets," running on a single page, which might help disentangle and smooth over some of your model/view logic.
From a classical MVC perspective, your views respond to changes in their associated models.
//initialize for view
initialize : function() {
this.model.on("change", this.render(), this);
}
The idea here is that anytime a view's model is changed, it'll render itself.
Alternatively or additionally, if you change something on a view, you can trigger an event that the controller listens to. If the controller also created the other models, it can modify them in some meaningful way, then if you're listening for changes to the models the views will change as well.
Similar answer to Chris Biscardi's. Here's what I have:
You create a global var Dispatcher (doesn't have to be global as long as it can be accessed from the scope of Backbone app):
Dispatcher = _.extend({}, Backbone.Events);
Dispatcher will help you execute subscribed callbacks with events that are not particularly triggered by changes in models or collections. And cool thing is that Dispatcher can execute any function, inside or outside Backbone app.
You subscribe to events using bind() in a view or any part of the app:
Dispatcher.bind('editor_keypress', this.validate_summary);
Then in another view or part of the app you trigger new event using trigger():
Dispatcher.trigger('redactor_keypress');
The beauty of using a dispatcher is its simplicity and ability to subscribe multiple listeners (e.g. callbacks in different Backbone views) to the same event.

Resources