This is a quote from Derick Baileys blog ( creator of Backbone.Marionette):
But a router should never instantiate a view and manipulate the DOM
directly with jQuery or placing the view in to the DOM
But I keep seeing code like this:
var Router = Backbone.Router.extend({
routes: {
"":home
},
home: function(){
// instance of a view object in a router
var homeView = new HomeView({model:model});
// do something with homeView
})
Can this be considered anti-pattern, although it is widely used ?
From my perspective, it is a good idea to separate definiton from instantiation of objects (calling new HomeView() later). But does this is to be found in router ?
Someone else asked the same question in the comments of the article you are referring to. Here is the answer Derick gave to which I agree, I am copying it in order to keep things centralized.
These two examples are functionally the same:
example 1
Backbone.Router.extend({
routes: {
"foo": "showFoo"
},
showFoo: function(){
var fooView = new FooView();
fooView.render();
$("#someEl").html(fooView.el);
}
});
example 2
Backbone.Router.extend({
routes: {
"foo": "showFoo"
},
showFoo: function(){
FooApp.show();
}
});
FooApp = {
show: function(){
var fooView = new FooView();
fooView.render();
$("#someEl").html(fooView.el);
}
}
The difference between the two is coupling, cohesion, encapsulation, separation of concerns and the single responsibility principle. If I want to change how the FooApp shows the application's views and make things work, I shouldn't have to change the Router. These two concerns should be separated. I should have "one reason to change" for the router and for the FooApp (high level application object).
For a small demo app, #1 is ok. Anything more than 1 or 2 views, though, and the code becomes unmaintainable quickly. Having to sift through all of the cruft of doing the actual display so that you can see what the router is doing as a whole is a really bad idea in my experience.
For a larger example of this at play, check out my BBCloneMail sample project: http://github.com/derickbailey... - look at the BBCloneMail.Router.js file.
I prefer initialize all view only once in router.initialize() and then just render them in routing methods. But I don't think it make such a big difference.
Related
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
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
I am using backbone.js to create a single page app. I am new to backbone, so please forgive any wrong semantics.
My Problem is when rendering the views.
Initially, I have a javascript in my index.html that executes the some dom manipulation(image slider).
The JS is wrapped in $(window).load() so all is fine on initiation.
The code obviously doesn't execute unless the page is loaded from url. the code will not run from backbone views or router. So the page loads without the dom manipulation.
I have tried to insert my code into the render and initialize function in the view, but to no avail. Should I add this code to the router? that seems to be a bit of a hack.
Where should I include the "dom ready" code?
and / or is there a better way to manage views and their dom elements on load in backbone?
the code:
home.js
window.HomeView = Backbone.View.extend({
initialize:function () {
this.render();
},
render:function () {
$(this.el).html(this.template());
this.startOrbits();
return this;
},
startOrbits:function(){
$(window).load(function() {
$('#orbit-main').orbit({ fluid: '16x6', swipe:true });
$('#orbit-news').orbit({ fluid: '8x6', bullets: true, directionalNav:false, captions:true, advanceSpeed: 9000});
});
},
});
But when I go to another view, then back, the code obviously doesn't
excite
I'm not quite sure what that means. Leaving the "excite" part aside, you don't "go to" views; views are just ways of adding elements to the page, or adding logic to existing elements.
If I had to guess though, I'd imagine that you're using the Backbone router to move between virtual "pages" (and you use views to make those pages). If that's the case, you need to look at the Backbone router events:
http://documentcloud.github.com/backbone/#Router
http://documentcloud.github.com/backbone/#FAQ-events
Specifically, I think you want to bind an event handler (on your router) to "route:nameOfYourRoute", or just :route" (if you want to trigger your logic on every virtual page load).
Hope that helps, and if my guesses are wrong please edit your question to clarify.
I was able to find a solution.
After commenting out the if statement in my router function, things went smoothly.
home: function () {
// if (!this.homeView) {
this.homeView = new HomeView();
// }
$('#main-content').html(this.homeView.el);
this.homeView.startOrbits();
this.headerView.selectMenuItem('home');
},
I do realize that this means I create a new view on every rout trigger.
Please feel free to offer more optimal solutions.
I found that most tutorials use one big router.
For example: https://github.com/thomasdavis/backboneboilerplate/blob/gh-pages/js/router.js
Wouldn't it be better to separate the routes (controllers) into separate files?
If yes how can I combine this with requirejs?
I think this is a question of preference. If you're doing a ginormous application with gazillion routes, then dividing your routers up is sensible. For small applications having just one big router is just fine.
If you decide to have multiple routers, make sure you don't have conflicting routes, so there won't be any unexpected behavior or errors.
So with requireJS: I think the best way would be to define each router in it's own file like this
define([blaa, blaa], function(Blaa, Blaa) {
var SubRouter1 = Backbone.Router.extend({
// work your routing magic here, remember to make no conflicting routes
});
return SubRouter1;
});
When you have all your desired routers set up you can bundle them up in the app.js
define([...,'subrouter1', 'subrouter2', ... , 'subrouterN', ...],
function(..., SubRouter1, SubRouter2, ... , SubRouterN, ...) {
// work your app magic here
initialize: function() { // or wherever you start your application
subrouter1 = new SubRouter1();
subrouter2 = new SubRouter2();
...
...
subrouterN = new SubRouterN();
Backbone.history.start(); // remember to start the history
},
// maybe work some more magic?
});
I've never done this myself, but I don't see why it wouldn't work if you keep the routes from conflicting. Hopefully this clears stuff for you.
check Backbone.js "fat router" design conundrum out : you can find #jakee answer there and some more options
Trying to create a page that allows users to add edit and view a parent child combined.
UI has 3 columns
Parent : List of Parents Children : Child
I want to configure the controllers(s) so that users can come back to right where they were but see no need to have it so both Parent and child can be editable.
// Getting closer using backbone marionette but still having some small issues
MyRouter = Backbone.Marionette.AppRouter.extend({
appRoutes: {
'': 'AddClient',
'View/:clientid': 'ViewClient',
'Edit/:clientid': 'EditClient',
'View/:clientid/Add': 'PolicyAdd',
'View/:clientid/View/:policyid': 'PolicyView',
'View/:clientid/Edit/:policyid': 'PolicyEdit'
}
});
someController = {
AddClient: function () {
var someView = new ClientAdd();
MyApp.clientPane.show(someView);
},
ViewClient: function (clientid) {
var someView = new ClientView();
MyApp.clientPane.show(someView);
},
EditClient: function (clientid) {
var someView = new ClientEdit();
MyApp.clientPane.show(someView);
},
PolicyAdd: function (clientid) {
this.ViewClient(clientid);
var someView = new PolicyAdd();
MyApp.policyPane.show(someView);
},
PolicyView: function (clientid, policyid) {
this.ViewClient(clientid);
var someView = new PolicyView();
MyApp.policyPane.show(someView);
},
PolicyEdit: function (clientid, policyid) {
this.ViewClient(clientid);
var someView = new PolicyEdit();
MyApp.policyPane.show(someView);
}
};
Having the "this.ViewClient" feels hacky and also doesn't work.
Multi-part answer, here...
"this.ViewClient is not a function"
this is a bug in Marionette. the controller method is called in the context of the router instead of the controller, so the call to this.ViewClient is trying to find it on the router.
oops.
bug logged. will fix asap. https://github.com/derickbailey/backbone.marionette/issues/38
--
UPDATE: this bug is now fixed in v0.5.1 of Backbone.Marionette https://github.com/derickbailey/backbone.marionette
--
to work around this issue for now, you can do this:
PolicyEdit: {
someController.ViewClient();
// ...
}
If that doesn't work, you may need to use Underscore.js' bind or bindAll methods to ensure the correct binding on your controller functions.
These workaround won't be necessary once i get the bug fixed... hopefully later today / tonight.
is basically calling other routes the best way to manipulate multiple regions?
The direct answer to this question is no.
But, you're not calling a route in this case. You're calling a method on your controller. That's perfectly fine - and actually, I would encourage this. It's a proper use of your object, and is one of the things that I think should be done instead of calling another route / router handler.
Routers And Controllers
A router is a feature, not an architectural requirement. Your app should work without a router, and a router should only add the ability to use bookmarks and the browser's forward/backward button.
With that philosophy in mind (which I know is controversial), using a controller like you have and calling multiple methods on your controller in order to get your application to the correct state, is one of the right approaches to take.
Look at it this way: if you removed the router from your app, you would be forced to call methods on your controller directly. To prevent duplication of code, you'll want to create many small methods on your controller that can do one thing very well, and then compose larger methods out of those smaller methods.
Hope that helps. :)