2

I'm currently writing a Backbone Marionette app which ultimately amounts to about 6 different "screens" or pages which will often times share content and I am unsure of how to best structure and access Regions.

I am using the app/module setup described here: StackOverflow question 11070408: How to define/use several routings using backbone and require.js. This will be an application which will have new functionality and content added to it over time and need to be scalable (and obviously as re-usable as possible)

The Single Page App I'm building has 4 primary sections on every screen: Header, Primary Content, Secondary Content, Footer.

The footer will be consistent across all pages, the header will be the same on 3 of the pages, and slightly modified (using about 80% of the same elements/content) on the remaining 3 pages. The "morecontent" region will be re-usable across various pages.

In my app.js file I'm defining my regions like so:

define(['views/LandingScreen', 'views/Header', 'router'], function(LandingScreen, Header, Router) {
    "use strict";
    var App = new Backbone.Marionette.Application();

    App.addRegions({
        header: '#mainHeader',
        maincontent: '#mainContent',
        morecontent: '#moreContent',
        footer: '#mainFooter'
    });

    App.addInitializer(function (options) {

    });

    App.on("initialize:after", function () {
        if (!Backbone.History.started) Backbone.history.start();
    });

    return App;
});

Now, referring back to the app setup in the aforementioned post, what would be the best way to handle the Regions. Would I independently re-declare each region in each sub-app? That seems to be the best way to keep modules as independent as possible. If I go that route, what would be the best way to open/close or hide/show those regions between the sub-apps?

Or, do I keep the Regions declared in app.js? If so, how would I then best alter and orchestrate events those regions from sub-apps? Having the Regions defined in the app.js file seems to be counter-intuitive to keeping what modules and the core app know about each other to a minimum. Plus, every example I see has the appRegions method in the main app file. What then is the best practice for accessing and changing those regions from the sub-app?

Thanks in advance!

Community
  • 1
  • 1
mindpivot
  • 341
  • 3
  • 17
  • Sorry for the delay, I've been debating AMD vs non-AMD based on the two answers I've gotten. For long-term plans, despite the fact I feel like I'm spending more time figuring out managing dependencies than the functionality of my app, it is going to be preferable to go the AMD route. – mindpivot Dec 21 '12 at 15:22

2 Answers2

4

I actually have a root app that takes care of starting up sub-applications, and it passes in the region in which they should display. I also use a custom component based off of Backbone.SubRoute that enables relative routing for sub-applications.

check out this gist: https://gist.github.com/4185418

You could easily adapt it to send a "config" object for addRegions that defines multiple regions, instead of the region value I'm sending to the sub-applications' start method

Keep in mind that whenever you call someRegion.show(view) in Marionette, it's going to first close whatever view is currently being shown in it. If you have two different regions, each defined in its own app, but both of which bind to the same DOM element, the only thing that matters is which region had show called most recently. That's messy, though, because you're not getting the advantages of closing the previous view - unbinding Event Binders, for example.

That's why, if I have a sub-app that "inherits" a region from some kind of root app, I usually just pass in the actual region instance from that root app to the sub-app, and save a reference to that region as a property of the sub-app. That way I can still call subApp.regionName.show(view) and it works perfectly - the only thing that might screw up is your event chain if you're trying to bubble events up from your region to your application (as the region will belong to the root app, rather than the sub-app). I get around this issue by almost always using a separate instance of Marionette.EventAggregator to manage events, rather than relying on the built-in capabilities of regions/views/controllers/etc.

That said, you can get the best of both worlds - you can pass the region instance into your sub-app, save a reference to it just so you can call "close", then use its regionInstance.el property to define your own region instance pointing to the same element.

for(var reg in regions) if regions.hasOwnProperty(reg) {
    var regionManager = Marionette.Region.buildRegion(regions[reg].el,
            Marionette.Region);
    thisApp[reg] = regionManager;
}

It all depends on what your priorities are.

Isochronous
  • 1,076
  • 10
  • 25
  • lsochronous, darn fine answer. I have a few questions on your gist. 1) Line 43 of Application.root.js: what would that abstraction look like? 2) Line 73 of the same, what is in the initialize:ui method? This is enough of a departure from how I was doing things that part throws me off. 3) In main.js, I assume the array which begins on line 18 is where I'd add further subApps to the entire application? 4) vent.super.js: Line 7 you say it should only be used by the root app. All other apps should use 'EchoVent'. What is EchoVent? Is it anything special or is a standard EventAggregator? – mindpivot Dec 21 '12 at 15:49
  • Quick answers first: 3) Yes 4) `EchoVent` is my term for a supervent that is instantiated with a namespace and at least one existing vent instance as parameters. Its initializer binds to the special "all" event and simply "echoes" it out across any vents that have been registered with the echovent, with the provided namespace prepended to the event name. So for example if I had done `var ev = new EchoVent("appName", rootVent)` and then triggered "app:started" on echoVent, it would trigger that event as expected, but also trigger `appName:app:started` on rootVent. – Isochronous Jan 02 '13 at 23:33
  • 1) I just created another gist with the answer to this question, because it's a bit long for the comments section here. Here's that link: https://gist.github.com/4439430. Oh, and I forgot to mention that I added the echovent code to the original gist, so if you want to see it, read the gist in my original answer. It's ugly, way over-engineered code though, so please don't judge me ;P – Isochronous Jan 02 '13 at 23:47
  • Sorry, last one. 2) It's mostly a semantic choice - I don't even have that code in my actual app anymore. It's using a Backbone.Wreqr command/execute pattern, as it's kind of a single point of responsibility thing - there's only one place that command has a handler, and that's in the root app's primary controller. We don't have a route that handles the root path (long story) so basically when the app starts up, I'm just sending a command to initialize the UI (display the default view), which the controller listens for. When it gets the command, it shows the default view in the main region. – Isochronous Jan 02 '13 at 23:48
  • All of that clears everything up, you are awesome. Thank you very much! – mindpivot Jan 03 '13 at 20:22
2

I personally prefer to use the modules in my Marionette application. I feel it removes the complexity that require.js adds to your application. In an app that I am currently working on, I've created one app.js file that defines my backbone application but I am using a controller module that loads my routes, fills my collections and populates my regions.

app.js ->

var app = new Backbone.Marionette.Application();
app.addRegions({
   region1: "#region1",
   region2: "#region2",
   region3: "#region3",
   region4: "#region4"
});

app.mainapp.js ->

app.module('MainApp', function(MainApp, App, Backbone, Marionette, $, _) {
   // AppObjects is an object that holds a collection for each region, 
   // this makes it accessible to other parts of the application
   // by calling app.MainApp.AppObjects.CollectionName.... 
   MainApp.AppObjects = new App.AppObjects.Core();

   MainApp.Controller = new Backbone.Marionette.Controller.extend({
     start: function() {
       // place some code here you want to run when the controller starts
     } //, you can place other methods inside your controller
   });

   // This code is ran by Marionette when the modules are loaded
   MainApp.addInitializer(function() {
     var controller = new MainApp.Controller();
     controller.start();
   });
});

You would then place your routes inside another module that will be accessed in the controller.

Then in the web page, you would start everything by calling.

$(function () {
    app.start();
});  

Marionette will automatically run and load all of your modules.

I hope this gets you started in some direction. Sorry I couldn't copy and past the entire application code to give you better examples. Once this project has been completed, I am going to recreate a demo app that I can push to the web.

Kalpers
  • 658
  • 5
  • 11
  • Kalpers, thanks for the input. I definitely was tempted to go the Module route but I ultimately don't feel it will give me the flexibility I will need down the road to ensure modularity of my apps. I work for a large association and will need to create modules which are as re-usable as possible. I know that in theory the modules should be written to be re-usable but in practice it is just too easy not to. Going AMD, as painful as it proves to be at times, is my somewhat begrudging choice. The module route would help me now but hurt me long term. Thank you very much for your thoughtful response – mindpivot Dec 21 '12 at 15:25