Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marionette Layout switching strategy

I have the following situation:

app.js: Singleton Marionette.Application() where I define a nav, a footer, and a main region. In the initializer I construct Marionette.Contoller's and attach them to the app's this.controller object for later control. I might not construct all the Controller's here, just the ones I want to Eagerly Load. Some are Lazy Loaded later. I also instantiate a Backbone.Router here, and pass in a reference to my app object:

var theApp = new TP.Application();

theApp.addRegions(
{
    navRegion: "#navigation",
    mainRegion: "#main",
    footerRegoin: "#footer"
});

theApp.addInitializer(function()
{
    // Set up controllers container and eagerly load all the required Controllers.
    this.controllers = {};

    this.controllers.navigationController = new NavigationController({ app: this });
    this.controllers.loginController = new LoginController({ app: this });
    this.controllers.calendarController = new CalendarController({ app: this });

    this.router = new Router({ app: this });
});

**Controller.js: this is a general use controller that handles view & model intsantiation and eventing. Each Controller owns its own Marionette.Layout, to be filled into the App.mainRegion. Each Controller binds to the layout's "show" event to fill in the layout's regions with custom views. Each Controller offers a getLayout() interface that returns the controller's associated layout.

Marionette.Controller.extend(
{
    getLayout: function() { return this.layout; },
    initialize: function()
    {
        this.views.myView = new MyView();
        ...
        this.layout.on("show", this.show, this);
        ...
    },
    show: function()
    {
        this.layout.myViewRegion.show(myView);
    }
});

router.js: the router uses the app singleton to load a Controller's layout into the App's main region:

...
routes:
{
    "home": "home",
    "login": "login",
    "calendar": "calendar",
    "": "calendar"  
},
home: function ()
{
    var lazyloadedController = new LazyLoadController();
    this.theApp.mainRegion.show(lazyLoadController.getLayout());
},
login: function (origin)
{
    this.theApp.mainRegion.show(this.theApp.controllers.loginController.layout);
}

As it is, everything works fine except for reloading the same layout / controller twice. What happens is that the DOM events defined in the LoginView do not re-bind on second show. Which is easily solved by moving the LoginView initialization code into the "show" event handler for that Controller:

LoginController = Marionette.Controller.extend(
{
    ...
    show: function()
    {
        if (this.views.loginView)
            delete this.views.loginView.close();

        this.views.loginView = new LoginView({ model: this.theApp.session });
        this.views.loginView.on("login:success", function()
        {
        });

        this.layout.mainRegion.show(this.views.loginView);
    }

Now everything works fine, but it undoes part of the reason I created Controller's to begin with: I want them to own a View and its Models, create them once, and not have to destroy & recreate them every time I switch layouts.

Am I missing something? Is this not how I should be using Layouts? Isn't the whole point of Layouts and Regions that I can switch in & out Views at will?

Obviously I wouldn't jump back to LoginController/Layout often, but what about between a HomeController/Layout, CalendarController/Layout, SummaryController/Layout, etc... in a single page application I might switch between those 'top-level' layouts rather often and I would want the view to stay cached in the background.

like image 915
Bernardo Avatar asked Feb 04 '13 02:02

Bernardo


1 Answers

I think your problem is that you don't maintain a single instance of the controller. The recommended way to handle routing/controllers (based on Brian Mann's videos) is like this

App.module('Routes', function (Routes, App, Backbone, Marionette, $, _) {

    // straight out of the book...
    var Router = Marionette.AppRouter.extend({
        appRoutes: {
            "home": "home",
            "login": "login",
            "calendar": "calendar"
        }
    });

    var API = {
        home: function () {
            App.Controller.go_home();
        },
        login: function () {
            App.Controller.go_login();
        },
        calendar: function () {
            App.Controller.go_calendar();
        }
    };

    App.addInitializer(function (options) {
        var router = new Router({controller: API});
    });
});

... and the controller:

App.module("Controller", function (Controller, App, Backbone, Marionette, $, _) {
    App.Controller = {
        go_home: function () {
            var layout = new App.Views.Main();
            layout.on('show', function () {
                // attach views to subregions here...
                var news = new App.Views.News();
                layout.newsRegion.show(news);
            });

            App.mainRegion.show(layout);
        },

        go_login: function () {
            ....
        },

        go_calendar: function () {
            ....
        }
    };
});

I suspect your problem is the lazy-loaded controller...

like image 122
aaronfay Avatar answered Oct 19 '22 11:10

aaronfay